Merge branch 'master' into process-improvement-v0.18.0

This commit is contained in:
Piotr Galar 2023-01-31 16:09:47 +01:00 committed by GitHub
commit 96a0eb2899
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
186 changed files with 6155 additions and 7057 deletions

View File

@ -7,7 +7,7 @@ jobs:
executor: continuation/default
steps:
- checkout
- run:
- run:
name: Generate params
# for builds on the ipfs/kubo repo, use 2xlarge for faster builds
# but since this is not available for many contributors, we otherwise use medium

View File

@ -143,14 +143,17 @@ jobs:
path: /tmp/circleci-test-results
sharness:
machine:
image: ubuntu-2004:202010-01
image: ubuntu-2204:2022.10.1
resource_class: << pipeline.parameters.resource_class >>
working_directory: ~/ipfs/kubo
environment:
<<: *default_environment
TEST_NO_DOCKER: 0
TEST_NO_PLUGIN: 1
TEST_NO_FUSE: 1
TEST_VERBOSE: 1
TEST_JUNIT: 1
TEST_EXPENSIVE: 1
steps:
- run: sudo apt update
- run: |
@ -159,7 +162,7 @@ jobs:
tar xfz go1.19.1.linux-amd64.tar.gz
echo "export PATH=$(pwd)/go/bin:\$PATH" >> ~/.bashrc
- run: go version
- run: sudo apt install socat net-tools fish
- run: sudo apt install socat net-tools fish libxml2-utils
- checkout
- run:
@ -183,7 +186,7 @@ jobs:
command: echo "export TEST_DOCKER_HOST=$(ip -4 addr show docker0 | grep -Po 'inet \K[\d.]+')" >> $BASH_ENV
- run:
echo TEST_DOCKER_HOST=$TEST_DOCKER_HOST &&
make -O -j << pipeline.parameters.make_jobs >> coverage/sharness_tests.coverprofile test/sharness/test-results/sharness.xml TEST_GENERATE_JUNIT=1 CONTINUE_ON_S_FAILURE=1 TEST_DOCKER_HOST=$TEST_DOCKER_HOST
make -O -j << pipeline.parameters.make_jobs >> test_sharness coverage/sharness_tests.coverprofile test/sharness/test-results/sharness.xml CONTINUE_ON_S_FAILURE=1 TEST_DOCKER_HOST=$TEST_DOCKER_HOST
- run:
when: always
command: bash <(curl -s https://codecov.io/bash) -cF sharness -X search -f coverage/sharness_tests.coverprofile
@ -345,13 +348,13 @@ jobs:
npx playwright install
working_directory: ~/ipfs/kubo/ipfs-webui
- run:
name: Running upstream tests (finish early if they fail)
name: Run ipfs-webui@main build and smoke-test to confirm the upstream repo is not broken
command: |
npm test || circleci-agent step halt
npm test
working_directory: ~/ipfs/kubo/ipfs-webui
- run:
name: Running tests with kubo built from current commit
command: npm test
name: Test ipfs-webui@main E2E against the locally built Kubo binary
command: npm run test:e2e
working_directory: ~/ipfs/kubo/ipfs-webui
environment:
IPFS_GO_EXEC: /tmp/circleci-workspace/bin/ipfs

200
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,200 @@
name: 'ci/gh-experiment: interop'
on:
workflow_dispatch:
pull_request:
push:
branches:
- 'master'
env:
GO_VERSION: 1.19.1
jobs:
prepare:
if: github.repository == 'ipfs/kubo' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
env:
TEST_NO_DOCKER: 1
TEST_NO_FUSE: 1
TEST_VERBOSE: 1
TRAVIS: 1
GIT_PAGER: cat
IPFS_CHECK_RCMGR_DEFAULTS: 1
defaults:
run:
shell: bash
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/checkout@v3
- uses: protocol/cache-go-action@v1
with:
name: ${{ github.job }}
- run: make build
- uses: actions/upload-artifact@v3
with:
name: kubo
path: cmd/ipfs/ipfs
ipfs-interop:
needs: [prepare]
runs-on: ubuntu-latest
strategy:
matrix:
suites:
- 'exchange-files'
- 'files pin circuit ipns cid-version-agnostic ipns-pubsub pubsub'
fail-fast: false
defaults:
run:
shell: bash
steps:
- uses: actions/setup-node@v3
with:
node-version: 16.12.0
- uses: actions/download-artifact@v3
with:
name: kubo
path: cmd/ipfs
- run: chmod +x cmd/ipfs/ipfs
- run: |
echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
id: npm-cache-dir
- uses: actions/cache@v3
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-${{ github.job }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-${{ github.job }}-
- run: mkdir interop
- run: |
npm init -y
npm install ipfs@^0.61.0
npm install ipfs-interop@^8.0.10
working-directory: interop
- run: npx ipfs-interop -- -t node $(sed -e 's#[^ ]*#-f test/&.js#g' <<< '${{ matrix.suites }}')
env:
LIBP2P_TCP_REUSEPORT: false
LIBP2P_ALLOW_WEAK_RSA_KEYS: 1
IPFS_GO_EXEC: ${{ github.workspace }}/cmd/ipfs/ipfs
working-directory: interop
go-ipfs-api:
needs: [prepare]
runs-on: ubuntu-latest
env:
TEST_NO_DOCKER: 1
TEST_NO_FUSE: 1
TEST_VERBOSE: 1
TRAVIS: 1
GIT_PAGER: cat
IPFS_CHECK_RCMGR_DEFAULTS: 1
defaults:
run:
shell: bash
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/download-artifact@v3
with:
name: kubo
path: cmd/ipfs
- run: chmod +x cmd/ipfs/ipfs
- uses: actions/checkout@v3
with:
repository: ipfs/go-ipfs-api
path: go-ipfs-api
- run: cmd/ipfs/ipfs daemon --init --enable-namesys-pubsub &
- run: |
while ! cmd/ipfs/ipfs id --api=/ip4/127.0.0.1/tcp/5001 2>/dev/null; do
sleep 1
done
timeout-minutes: 5
- uses: protocol/cache-go-action@v1
with:
name: ${{ github.job }}
- run: go test -count=1 -v ./...
working-directory: go-ipfs-api
- run: cmd/ipfs/ipfs shutdown
if: always()
go-ipfs-http-client:
needs: [prepare]
runs-on: ubuntu-latest
env:
TEST_NO_DOCKER: 1
TEST_NO_FUSE: 1
TEST_VERBOSE: 1
TRAVIS: 1
GIT_PAGER: cat
IPFS_CHECK_RCMGR_DEFAULTS: 1
defaults:
run:
shell: bash
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- uses: actions/download-artifact@v3
with:
name: kubo
path: cmd/ipfs
- run: chmod +x cmd/ipfs/ipfs
- uses: actions/checkout@v3
with:
repository: ipfs/go-ipfs-http-client
path: go-ipfs-http-client
- uses: protocol/cache-go-action@v1
with:
name: ${{ github.job }}
- run: echo '${{ github.workspace }}/cmd/ipfs' >> $GITHUB_PATH
- run: go test -count=1 -v ./...
working-directory: go-ipfs-http-client
ipfs-webui:
needs: [prepare]
runs-on: ubuntu-latest
env:
NO_SANDBOX: true
LIBP2P_TCP_REUSEPORT: false
LIBP2P_ALLOW_WEAK_RSA_KEYS: 1
E2E_IPFSD_TYPE: go
TRAVIS: 1
GIT_PAGER: cat
IPFS_CHECK_RCMGR_DEFAULTS: 1
defaults:
run:
shell: bash
steps:
- uses: actions/setup-node@v3
with:
node-version: 16.12.0
- uses: actions/download-artifact@v3
with:
name: kubo
path: cmd/ipfs
- run: chmod +x cmd/ipfs/ipfs
- uses: actions/checkout@v3
with:
repository: ipfs/ipfs-webui
path: ipfs-webui
- run: |
echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
id: npm-cache-dir
- uses: actions/cache@v3
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-${{ github.job }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-${{ github.job }}-
- run: |
npm ci --prefer-offline --no-audit --progress=false
npx playwright install
working-directory: ipfs-webui
- name: Run ipfs-webui@main build and smoke-test to confirm the upstream repo is not broken
run: npm test
working-directory: ipfs-webui
- name: Test ipfs-webui@main E2E against the locally built Kubo binary
run: npm run test:e2e
env:
IPFS_GO_EXEC: ${{ github.workspace }}/cmd/ipfs/ipfs
working-directory: ipfs-webui

View File

@ -1,5 +1,5 @@
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
name: "CodeQL"
name: CodeQL
on:
workflow_dispatch:

25
.github/workflows/docker-build.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: 'ci/gh-experiment: docker-build'
on:
workflow_dispatch:
pull_request:
push:
branches:
- 'master'
jobs:
docker-build:
if: github.repository == 'ipfs/kubo' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
env:
IMAGE_NAME: ipfs/kubo
WIP_IMAGE_TAG: wip
defaults:
run:
shell: bash
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.19.1
- uses: actions/checkout@v3
- run: docker build -t $IMAGE_NAME:$WIP_IMAGE_TAG .

View File

@ -30,15 +30,15 @@ jobs:
- name: Get tags
id: tags
run: |
TAGS="$(./bin/get-docker-tags.sh $(date -u +%F))"
TAGS="${TAGS//$'\n'/'%0A'}"
echo "::set-output name=value::$(echo $TAGS)"
echo "value<<EOF" >> $GITHUB_OUTPUT
./bin/get-docker-tags.sh "$(date -u +%F)" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
shell: bash
- name: Log in to Docker Hub
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with:
username: ${{ secrets.DOCKER_USERNAME }}
username: ${{ vars.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build Docker image and publish to Docker Hub

39
.github/workflows/gobuild.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: 'ci/gh-experiment: go build'
on:
workflow_dispatch:
pull_request:
push:
branches:
- 'master'
jobs:
runner:
if: github.repository == 'ipfs/kubo' || github.event_name == 'workflow_dispatch'
uses: ipfs/kubo/.github/workflows/runner.yml@master
gobuild:
needs: [runner]
runs-on: ${{ fromJSON(needs.runner.outputs.config).labels }}
env:
TEST_NO_DOCKER: 1
TEST_VERBOSE: 1
TRAVIS: 1
GIT_PAGER: cat
IPFS_CHECK_RCMGR_DEFAULTS: 1
defaults:
run:
shell: bash
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.19.1
- uses: actions/checkout@v3
- uses: protocol/cache-go-action@v1
with:
name: ${{ github.job }}
- run: make cmd/ipfs-try-build
env:
TEST_NO_FUSE: 0
- run: make cmd/ipfs-try-build
env:
TEST_NO_FUSE: 1

View File

@ -1,8 +1,15 @@
on: [push, pull_request]
name: Go Checks
on:
workflow_dispatch:
pull_request:
push:
branches:
- 'master'
jobs:
unit:
if: github.repository == 'ipfs/kubo' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
name: All
steps:

32
.github/workflows/golint.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: 'ci/gh-experiment: go lint'
on:
workflow_dispatch:
pull_request:
push:
branches:
- 'master'
jobs:
golint:
if: github.repository == 'ipfs/kubo' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
env:
TEST_NO_DOCKER: 1
TEST_NO_FUSE: 1
TEST_VERBOSE: 1
TRAVIS: 1
GIT_PAGER: cat
IPFS_CHECK_RCMGR_DEFAULTS: 1
defaults:
run:
shell: bash
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.19.1
- uses: actions/checkout@v3
- uses: protocol/cache-go-action@v1
with:
name: ${{ github.job }}
- run: make -O test_go_lint

65
.github/workflows/gotest.yml vendored Normal file
View File

@ -0,0 +1,65 @@
name: 'ci/gh-experiment: go test'
on:
workflow_dispatch:
pull_request:
push:
branches:
- 'master'
jobs:
gotest:
if: github.repository == 'ipfs/kubo' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
env:
TEST_NO_DOCKER: 1
TEST_NO_FUSE: 1
TEST_VERBOSE: 1
TRAVIS: 1
GIT_PAGER: cat
IPFS_CHECK_RCMGR_DEFAULTS: 1
defaults:
run:
shell: bash
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.19.1
- uses: actions/checkout@v3
- uses: protocol/cache-go-action@v1
with:
name: ${{ github.job }}
- run: |
make -j 1 test/unit/gotest.junit.xml &&
[[ ! $(jq -s -c 'map(select(.Action == "fail")) | .[]' test/unit/gotest.json) ]]
- uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0
if: always()
with:
name: unittests
files: coverage/unit_tests.coverprofile
- run: |
# we want to first test with the kubo version in the go.mod file
go test -v ./...
# we also want to test the examples against the current version of kubo
# however, that version might be in a fork so we need to replace the dependency
# backup the go.mod and go.sum files to restore them after we run the tests
cp go.mod go.mod.bak
cp go.sum go.sum.bak
# make sure the examples run against the current version of kubo
go mod edit -replace github.com/ipfs/kubo=./../../..
go mod tidy
go test -v ./...
# restore the go.mod and go.sum files to their original state
mv go.mod.bak go.mod
mv go.sum.bak go.sum
working-directory: docs/examples/kubo-as-a-library
- uses: actions/upload-artifact@v3
with:
name: unit
path: test/unit/gotest.junit.xml
if: always()

33
.github/workflows/runner.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: 'ci/gh-experiment: choose runner'
on:
workflow_call:
outputs:
config:
description: "The runner's configuration"
value: ${{ jobs.choose.outputs.config }}
jobs:
choose:
runs-on: ubuntu-latest
outputs:
config: ${{ steps.config.outputs.result }}
steps:
- uses: actions/github-script@v6
id: config
with:
script: |
if (`${context.repo.owner}/${context.repo.repo}` === 'ipfs/kubo') {
return {
labels: ['self-hosted', 'linux', 'x64', 'kubo'],
parallel: 10,
aws: true
}
} else {
return {
labels: ['ubuntu-latest'],
parallel: 3,
aws: false
}
}
- run: echo ${{ steps.config.outputs.result }}

124
.github/workflows/sharness.yml vendored Normal file
View File

@ -0,0 +1,124 @@
name: 'ci/gh-experiment: sharness'
on:
workflow_dispatch:
pull_request:
push:
branches:
- 'master'
jobs:
runner:
if: github.repository == 'ipfs/kubo' || github.event_name == 'workflow_dispatch'
uses: ipfs/kubo/.github/workflows/runner.yml@master
sharness:
needs: [runner]
runs-on: ${{ fromJSON(needs.runner.outputs.config).labels }}
defaults:
run:
shell: bash
steps:
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.19.1
- name: Checkout Kubo
uses: actions/checkout@v3
with:
path: kubo
- name: Install missing tools
run: sudo apt install -y socat net-tools fish libxml2-utils
- name: Checkout IPFS Pinning Service API
uses: actions/checkout@v3
with:
repository: ipfs-shipyard/rb-pinning-service-api
ref: 773c3adbb421c551d2d89288abac3e01e1f7c3a8
path: rb-pinning-service-api
# TODO: check if docker compose (not docker-compose) is available on default gh runners
- name: Start IPFS Pinning Service API
run: |
(for i in {1..3}; do docker compose pull && break || sleep 5; done) &&
docker compose up -d
working-directory: rb-pinning-service-api
- name: Restore Go Cache
uses: protocol/cache-go-action@v1
with:
name: ${{ github.job }}
- name: Find IPFS Pinning Service API address
run: echo "TEST_DOCKER_HOST=$(ip -4 addr show docker0 | grep -Po 'inet \K[\d.]+')" >> $GITHUB_ENV
- uses: actions/cache@v3
with:
path: test/sharness/lib/dependencies
key: ${{ runner.os }}-test-generate-junit-html-${{ hashFiles('test/sharness/lib/test-generate-junit-html.sh') }}
- name: Run Sharness tests
run: |
make -O -j "$PARALLEL" \
test_sharness \
coverage/sharness_tests.coverprofile \
test/sharness/test-results/sharness.xml \
test/sharness/test-results/sharness.html \
test/sharness/test-results/sharness-html
working-directory: kubo
env:
TEST_NO_DOCKER: 0
TEST_NO_PLUGIN: 1
TEST_NO_FUSE: 1
TEST_VERBOSE: 1
TEST_JUNIT: 1
TEST_EXPENSIVE: 1
IPFS_CHECK_RCMGR_DEFAULTS: 1
CONTINUE_ON_S_FAILURE: 1
PARALLEL: ${{ fromJSON(needs.runner.outputs.config).parallel }}
- name: Upload coverage report
uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # v3.1.0
if: failure() || success()
with:
name: sharness
files: kubo/coverage/sharness_tests.coverprofile
- name: Aggregate results
run: find kubo/test/sharness/test-results -name 't*-*.sh.*.counts' | kubo/test/sharness/lib/sharness/aggregate-results.sh > kubo/test/sharness/test-results/summary.txt
- name: 👉️ If this step failed, go to «Summary» (top left) → «HTML Report» → inspect the «Failures» column
run: |
cat kubo/test/sharness/test-results/summary.txt &&
grep 'failed\s*0' kubo/test/sharness/test-results/summary.txt
- name: Add aggregate results to the summary
if: failure() || success()
run: |
echo "# Summary" >> $GITHUB_STEP_SUMMARY
echo >> $GITHUB_STEP_SUMMARY
cat kubo/test/sharness/test-results/summary.txt >> $GITHUB_STEP_SUMMARY
- name: Upload one-page HTML report to S3
id: one-page
uses: pl-strflt/tf-aws-gh-runner/.github/actions/upload-artifact@main
if: fromJSON(needs.runner.outputs.config).aws && (failure() || success())
with:
source: kubo/test/sharness/test-results/sharness.html
destination: sharness.html
- name: Upload one-page HTML report
if: (! fromJSON(needs.runner.outputs.config).aws) && (failure() || success())
uses: actions/upload-artifact@v3
with:
name: sharness.html
path: kubo/test/sharness/test-results/sharness.html
- name: Upload full HTML report to S3
id: full
uses: pl-strflt/tf-aws-gh-runner/.github/actions/upload-artifact@main
if: fromJSON(needs.runner.outputs.config).aws && (failure() || success())
with:
source: kubo/test/sharness/test-results/sharness-html
destination: sharness-html/
- name: Upload full HTML report
if: (! fromJSON(needs.runner.outputs.config).aws) && (failure() || success())
uses: actions/upload-artifact@v3
with:
name: sharness-html
path: kubo/test/sharness/test-results/sharness-html
- name: Add S3 links to the summary
if: fromJSON(needs.runner.outputs.config).aws && (failure() || success())
run: echo "$MD" >> $GITHUB_STEP_SUMMARY
env:
MD: |
# HTML Reports
- View the [one page HTML report](${{ steps.one-page.outputs.url }})
- View the [full HTML report](${{ steps.full.outputs.url }}index.html)

View File

@ -1,3 +1,8 @@
linters:
enable:
- stylecheck
linters-settings:
stylecheck:
dot-import-whitelist:
- github.com/ipfs/kubo/test/cli/testutils

View File

@ -1,5 +1,7 @@
# Kubo Changelogs
- [v0.19](docs/changelogs/v0.19.md)
- [v0.18](docs/changelogs/v0.18.md)
- [v0.17](docs/changelogs/v0.17.md)
- [v0.16](docs/changelogs/v0.16.md)
- [v0.15](docs/changelogs/v0.15.md)

View File

@ -62,13 +62,13 @@ Before opening an issue, consider using one of the following locations to ensure
- [openSUSE](#opensuse)
- [Guix](#guix)
- [Snap](#snap)
- [Unofficial MacOS packages](#unofficial-macos-packages)
- [MacPorts](#macports)
- [Nix](#nix-1)
- [Homebrew](#homebrew)
- [Unofficial Windows packages](#unofficial-windows-packages)
- [Chocolatey](#chocolatey)
- [Scoop](#scoop)
- [Unofficial MacOS packages](#unofficial-macos-packages)
- [MacPorts](#macports)
- [Nix](#nix-macos)
- [Homebrew](#homebrew)
- [Build from Source](#build-from-source)
- [Install Go](#install-go)
- [Download and Compile IPFS](#download-and-compile-ipfs)
@ -166,10 +166,12 @@ $ ipfs get /ipns/dist.ipfs.tech/kubo/$VERSION/kubo_$VERSION_windows-amd64.zip
### Unofficial Linux packages
- [Arch Linux](#arch-linux)
- [Nix](#nix-linux)
- [ArchLinux](#arch-linux)
- [Nix](#nix)
- [Solus](#solus)
- [openSUSE](#opensuse)
- [Guix](#guix)
- [Snap](#snap)
#### Arch Linux
@ -193,11 +195,10 @@ You can also install the Package by using its attribute name, which is also `ipf
#### Solus
In solus, kubo (go-ipfs) is available in the main repository as
[go-ipfs](https://dev.getsol.us/source/go-ipfs/repository/master/).
[Package for Solus](https://dev.getsol.us/source/kubo/repository/master/)
```
$ sudo eopkg install go-ipfs
$ sudo eopkg install kubo
```
You can also install it through the Solus software center.
@ -208,16 +209,36 @@ You can also install it through the Solus software center.
#### Guix
GNU's functional package manager, [Guix](https://www.gnu.org/software/guix/), also provides a go-ipfs package:
```
$ guix package -i go-ipfs
```
[Community Package for go-ipfs](https://packages.guix.gnu.org/packages/go-ipfs/0.11.0/) is no out-of-date.
#### Snap
No longer supported, see rationale in [kubo#8688](https://github.com/ipfs/kubo/issues/8688).
### Unofficial Windows packages
- [Chocolatey](#chocolatey)
- [Scoop](#scoop)
#### Chocolatey
No longer supported, see rationale in [kubo#9341](https://github.com/ipfs/kubo/issues/9341).
#### Scoop
Scoop provides kubo as `kubo` in its 'extras' bucket.
```Powershell
PS> scoop bucket add extras
PS> scoop install kubo
```
### Unofficial macOS packages
- [MacPorts](#macports)
- [Nix](#nix-macos)
- [Homebrew](#homebrew)
#### MacPorts
The package [ipfs](https://ports.macports.org/port/ipfs) currently points to kubo (go-ipfs) and is being maintained.
@ -244,31 +265,6 @@ A Homebrew formula [ipfs](https://formulae.brew.sh/formula/ipfs) is maintained t
$ brew install --formula ipfs
```
### Unofficial Windows packages
- [Chocolatey](#chocolatey)
- [Scoop](#scoop)
#### Chocolatey
No longer supported, see rationale in [kubo#9341](https://github.com/ipfs/kubo/issues/9341).
#### Scoop
Scoop provides kubo as `kubo` in its 'extras' bucket.
```Powershell
PS> scoop bucket add extras
PS> scoop install kubo
```
### Unofficial macOS packages
- [MacPorts](#macports)
- [Nix](#nix-macos)
- [Homebrew](#homebrew)
### Build from Source
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/ipfs/kubo?label=Requires%20Go&logo=go&style=flat-square&cacheSeconds=3600)

View File

@ -136,8 +136,7 @@ help:
@echo ' test_go_expensive - Run all go tests and compile on all platforms'
@echo ' test_go_race - Run go tests with the race detector enabled'
@echo ' test_go_lint - Run the `golangci-lint` vetting tool'
@echo ' test_sharness_short - Run short sharness tests'
@echo ' test_sharness_expensive - Run all sharness tests'
@echo ' test_sharness - Run sharness tests'
@echo ' coverage - Collects coverage info from unit tests and sharness'
@echo
.PHONY: help

View File

@ -14,8 +14,8 @@ environment:
GOPATH: c:\gopath
TEST_VERBOSE: 1
#TEST_NO_FUSE: 1
#TEST_SUITE: test_sharness_expensive
#GOFLAGS: -tags nofuse
#TEST_SUITE: test_sharness
#GOFLAGS: -tags nofuse
global:
BASH: C:\cygwin\bin\bash
matrix:
@ -23,27 +23,27 @@ environment:
GOVERSION: 1.5.1
GOROOT: c:\go
DOWNLOADPLATFORM: "x64"
install:
# Enable make
#- SET PATH=c:\MinGW\bin;%PATH%
#- copy c:\MinGW\bin\mingw32-make.exe c:\MinGW\bin\make.exe
- go version
- go env
# Cygwin build script
#
# NOTES:
#
# The stdin/stdout file descriptor appears not to be valid for the Appveyor
# build which causes failures as certain functions attempt to redirect
# build which causes failures as certain functions attempt to redirect
# default file handles. Ensure a dummy file descriptor is opened with 'exec'.
#
#
build_script:
- '%BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; export PATH=$GOPATH/bin:$PATH; make nofuse"'
test_script:
- '%BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; export PATH=$GOPATH/bin:$PATH; export GOFLAGS=''-tags nofuse''; export TEST_NO_FUSE=1; export TEST_VERBOSE=1; export TEST_SUITE=test_sharness_expensive; make $TEST_SUITE"'
- '%BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; export PATH=$GOPATH/bin:$PATH; export GOFLAGS=''-tags nofuse''; export TEST_NO_FUSE=1; export TEST_VERBOSE=1; export TEST_EXPENSIVE=1; export TEST_SUITE=test_sharness; make $TEST_SUITE"'
#build:
# parallel: true

View File

@ -3,13 +3,3 @@
This directory contains the go-ipfs assets:
* Getting started documentation (`init-doc`).
* Directory listing HTML template (`dir-index-html`).
## Re-generating
Edit the source files and use `go generate` from within the
assets directory:
```
go generate .
```

View File

@ -1,30 +1,22 @@
//go:generate npm run build --prefix ./dir-index-html/
package assets
import (
"embed"
"fmt"
"io"
"io/fs"
gopath "path"
"strconv"
"github.com/ipfs/kubo/core"
"github.com/ipfs/kubo/core/coreapi"
"github.com/cespare/xxhash"
cid "github.com/ipfs/go-cid"
files "github.com/ipfs/go-ipfs-files"
"github.com/ipfs/go-libipfs/files"
options "github.com/ipfs/interface-go-ipfs-core/options"
"github.com/ipfs/interface-go-ipfs-core/path"
)
//go:embed init-doc dir-index-html/dir-index.html dir-index-html/knownIcons.txt
//go:embed init-doc
var Asset embed.FS
// AssetHash a non-cryptographic hash of all embedded assets
var AssetHash string
// initDocPaths lists the paths for the docs we want to seed during --init
var initDocPaths = []string{
gopath.Join("init-doc", "about"),
@ -36,32 +28,6 @@ var initDocPaths = []string{
gopath.Join("init-doc", "ping"),
}
func init() {
sum := xxhash.New()
err := fs.WalkDir(Asset, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
file, err := Asset.Open(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(sum, file)
return err
})
if err != nil {
panic("error creating asset sum: " + err.Error())
}
AssetHash = strconv.FormatUint(sum.Sum64(), 32)
}
// SeedInitDocs adds the list of embedded init documentation to the passed node, pins it and returns the root key
func SeedInitDocs(nd *core.IpfsNode) (cid.Cid, error) {
return addAssetList(nd, initDocPaths)

View File

@ -1,3 +0,0 @@
# dag-index-html
> HTML representation for non-UnixFS DAGs such as DAG-CBOR.

File diff suppressed because one or more lines are too long

View File

@ -1,26 +0,0 @@
# dir-index-html
> Directory listing HTML for HTTP gateway
![](https://user-images.githubusercontent.com/157609/88379209-ce6f0600-cda2-11ea-9620-20b9237bb441.png)
## Updating
When making updates to the directory listing page template, please note the following:
1. Make your changes to the (human-friendly) source documents in the `src` directory and run `npm run build`
3. Before testing or releasing, go to the top-level `./assets` directory and make sure to run the `go generate .` script to update the bindata version
## Testing
1. Make sure you have [Go](https://golang.org/dl/) installed
2. Start the test server, which lives in its own directory:
```bash
> cd test
> go run .
```
This will listen on [`localhost:3000`](http://localhost:3000/) and reload the template every time you refresh the page.
If you get a "no such file or directory" error upon trying `go run .`, make sure you ran `npm run build` to generate the minified artifact that the test is looking for.

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
package dirindexhtml

View File

@ -1,65 +0,0 @@
.aac
.aiff
.ai
.avi
.bmp
.c
.cpp
.css
.dat
.dmg
.doc
.dotx
.dwg
.dxf
.eps
.exe
.flv
.gif
.h
.hpp
.html
.ics
.iso
.java
.jpg
.jpeg
.js
.key
.less
.mid
.mkv
.mov
.mp3
.mp4
.mpg
.odf
.ods
.odt
.otp
.ots
.ott
.pdf
.php
.png
.ppt
.psd
.py
.qt
.rar
.rb
.rtf
.sass
.scss
.sql
.tga
.tgz
.tiff
.txt
.wav
.wmv
.xls
.xlsx
.xml
.yml
.zip

View File

@ -1,17 +0,0 @@
{
"name": "dir-index-html",
"description": "Directory listing HTML for go-ipfs gateways",
"version": "1.3.0",
"private": true,
"homepage": "https://github.com/ipfs/go-ipfs",
"license": "MIT",
"scripts": {
"start": "cd test && go run .",
"build": "npm run build:clean && npm run build:remove-style-links && npm run build:minify-wrap-css && npm run build:combine-html-css && npm run build:remove-unused",
"build:clean": "rm dir-index.html",
"build:remove-style-links": "sed '/<link rel=\"stylesheet\"/d' ./src/dir-index.html > ./base-html.html",
"build:minify-wrap-css": "(echo \"<style>\" && cat ./src/icons.css ./src/style.css | tr -d \"\t\n\r\" && echo && echo \"</style>\") > ./minified-wrapped-style.html",
"build:combine-html-css": "sed '/<\\/title>/ r ./minified-wrapped-style.html' ./base-html.html > ./dir-index.html",
"build:remove-unused": "rm ./base-html.html && rm ./minified-wrapped-style.html"
}
}

View File

@ -1,98 +0,0 @@
<!DOCTYPE html>
{{ $root := . }}
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="description" content="A directory of content-addressed files hosted on IPFS">
<meta property="og:title" content="Files on IPFS">
<meta property="og:description" content="{{ .Path }}">
<meta property="og:type" content="website">
<meta property="og:image" content="https://gateway.ipfs.io/ipfs/QmSDeYAe9mga6NdTozAZuyGL3Q1XjsLtvX28XFxJH8oPjq">
<meta name="twitter:title" content="{{ .Path }}">
<meta name="twitter:description" content="A directory of files hosted on the distributed, decentralized web using IPFS">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://gateway.ipfs.io/ipfs/QmSDeYAe9mga6NdTozAZuyGL3Q1XjsLtvX28XFxJH8oPjq">
<meta name="twitter:creator" content="@ipfs">
<meta name="twitter:site" content="@ipfs">
<meta name="image" content="https://gateway.ipfs.io/ipfs/QmSDeYAe9mga6NdTozAZuyGL3Q1XjsLtvX28XFxJH8oPjq">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlo89/56ZQ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACUjDu1lo89/6mhTP+zrVP/nplD/5+aRK8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHNiIS6Wjz3/ubFY/761W/+vp1D/urRZ/8vDZf/GvmH/nplD/1BNIm8AAAAAAAAAAAAAAAAAAAAAAAAAAJaPPf+knEj/vrVb/761W/++tVv/r6dQ/7q0Wf/Lw2X/y8Nl/8vDZf+tpk7/nplD/wAAAAAAAAAAAAAAAJaPPf+2rVX/vrVb/761W/++tVv/vrVb/6+nUP+6tFn/y8Nl/8vDZf/Lw2X/y8Nl/8G6Xv+emUP/AAAAAAAAAACWjz3/vrVb/761W/++tVv/vrVb/761W/+vp1D/urRZ/8vDZf/Lw2X/y8Nl/8vDZf/Lw2X/nplD/wAAAAAAAAAAlo89/761W/++tVv/vrVb/761W/++tVv/r6dQ/7q0Wf/Lw2X/y8Nl/8vDZf/Lw2X/y8Nl/56ZQ/8AAAAAAAAAAJaPPf++tVv/vrVb/761W/++tVv/vbRa/5aPPf+emUP/y8Nl/8vDZf/Lw2X/y8Nl/8vDZf+emUP/AAAAAAAAAACWjz3/vrVb/761W/++tVv/vrVb/5qTQP+inkb/op5G/6KdRv/Lw2X/y8Nl/8vDZf/Lw2X/nplD/wAAAAAAAAAAlo89/761W/++tVv/sqlS/56ZQ//LxWb/0Mlp/9DJaf/Kw2X/oJtE/7+3XP/Lw2X/y8Nl/56ZQ/8AAAAAAAAAAJaPPf+9tFr/mJE+/7GsUv/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav+xrFL/nplD/8vDZf+emUP/AAAAAAAAAACWjz3/op5G/9HKav/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav+inkb/nplD/wAAAAAAAAAAAAAAAKKeRv+3slb/0cpq/9HKav/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav+1sFX/op5G/wAAAAAAAAAAAAAAAAAAAAAAAAAAop5GUKKeRv/Nxmf/0cpq/9HKav/Rymr/0cpq/83GZ/+inkb/op5GSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAop5G16KeRv/LxWb/y8Vm/6KeRv+inkaPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAop5G/6KeRtcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/n8AAPgfAADwDwAAwAMAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAAwAMAAPAPAAD4HwAA/n8AAA==" />
<link rel="stylesheet" href="style.css"/>
<link rel="stylesheet" href="icons.css">
<title>{{ .Path }}</title>
</head>
<body>
<div id="page-header">
<div id="page-header-logo" class="ipfs-logo">&nbsp;</div>
<div id="page-header-menu">
<div class="menu-item-wide"><a href="https://ipfs.tech" target="_blank" rel="noopener noreferrer">About IPFS</a></div>
<div class="menu-item-wide"><a href="https://ipfs.tech#install" target="_blank" rel="noopener noreferrer">Install IPFS</a></div>
<div class="menu-item-narrow"><a href="https://ipfs.tech" target="_blank" rel="noopener noreferrer">About</a></div>
<div class="menu-item-narrow"><a href="https://ipfs.tech#install" target="_blank" rel="noopener noreferrer">Install</a></div>
<div>
<a href="https://github.com/ipfs/kubo/issues/new/choose" target="_blank" rel="noopener noreferrer" title="Report a bug">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18.4 21"><circle cx="7.5" cy="4.8" r="1"/><circle cx="11.1" cy="4.8" r="1"/><path d="M12.7 8.4c-0.5-1.5-1.9-2.5-3.5-2.5 -1.6 0-3 1-3.5 2.5H12.7z"/><path d="M8.5 9.7H5c-0.5 0.8-0.7 1.7-0.7 2.7 0 2.6 1.8 4.8 4.2 5.2V9.7z"/><path d="M13.4 9.7H9.9v7.9c2.4-0.4 4.2-2.5 4.2-5.2C14.1 11.4 13.9 10.5 13.4 9.7z"/><circle cx="15.7" cy="12.9" r="1"/><circle cx="15.1" cy="15.4" r="1"/><circle cx="15.3" cy="10.4" r="1"/><circle cx="2.7" cy="12.9" r="1"/><circle cx="3.3" cy="15.4" r="1"/><circle cx="3.1" cy="10.4" r="1"/></svg>
</a>
</div>
</div>
</div>
<div id="content">
<div id="content-header" class="d-flex flex-wrap">
<div>
<strong>
Index of
{{ range .Breadcrumbs -}}
/{{ if .Path }}<a href="{{ $root.GatewayURL }}{{ .Path | urlEscape }}">{{ .Name }}</a>{{ else }}{{ .Name }}{{ end }}
{{- else }}
{{ .Path }}
{{ end }}
</strong>
{{ if .Hash }}
<div class="ipfs-hash" translate="no">
{{ .Hash }}
</div>
{{ end }}
</div>
{{ if .Size }}
<div class="no-linebreak flex-shrink-1 ml-auto">
<strong>&nbsp;{{ .Size }}</strong>
</div>
{{ end }}
</div>
<div class="table-responsive">
<table>
{{ if .BackLink }}
<tr>
<td class="type-icon">
<div class="ipfs-_blank">&nbsp;</div>
</td>
<td>
<a href="{{.BackLink | urlEscape}}">..</a>
</td>
<td></td>
<td></td>
</tr>
{{ end }}
{{ range .Listing }}
<tr>
<td class="type-icon">
<div class="{{iconFromExt .Name}}">&nbsp;</div>
</td>
<td>
<a href="{{ .Path | urlEscape }}">{{ .Name }}</a>
</td>
<td class="no-linebreak">
{{ if .Hash }}
<a class="ipfs-hash" translate="no" href={{ if $root.DNSLink }}"https://cid.ipfs.tech/#{{ .Hash | urlEscape}}" target="_blank" rel="noreferrer noopener"{{ else }}"{{ $root.GatewayURL }}/ipfs/{{ .Hash | urlEscape}}?filename={{ .Name | urlEscape }}"{{ end }}>
{{ .ShortHash }}
</a>
{{ end }}
</td>
<td class="no-linebreak">{{ .Size }}</td>
</tr>
{{ end }}
</table>
</div>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,212 +0,0 @@
body {
color:#34373f;
font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;
font-size:14px;
line-height:1.43;
margin:0;
word-break:break-all;
-webkit-text-size-adjust:100%;
-ms-text-size-adjust:100%;
-webkit-tap-highlight-color:transparent
}
a {
color:#117eb3;
text-decoration:none
}
a:hover {
color:#00b0e9;
text-decoration:underline
}
a:active,
a:visited {
color:#00b0e9
}
strong {
font-weight:700
}
table {
border-collapse:collapse;
border-spacing:0;
max-width:100%;
width:100%
}
table:last-child {
border-bottom-left-radius:3px;
border-bottom-right-radius:3px
}
tr:first-child td {
border-top:0
}
tr:nth-of-type(even) {
background-color:#f7f8fa
}
td {
border-top:1px solid #d9dbe2;
padding:.65em;
vertical-align:top
}
#page-header {
align-items:center;
background:#0b3a53;
border-bottom:4px solid #69c4cd;
color:#fff;
display:flex;
font-size:1.12em;
font-weight:500;
justify-content:space-between;
padding:0 1em
}
#page-header a {
color:#69c4cd
}
#page-header a:active {
color:#9ad4db
}
#page-header a:hover {
color:#fff
}
#page-header-logo {
height:2.25em;
margin:.7em .7em .7em 0;
width:7.15em
}
#page-header-menu {
align-items:center;
display:flex;
margin:.65em 0
}
#page-header-menu div {
margin:0 .6em
}
#page-header-menu div:last-child {
margin:0 0 0 .6em
}
#page-header-menu svg {
fill:#69c4cd;
height:1.8em;
margin-top:.125em
}
#page-header-menu svg:hover {
fill:#fff
}
.menu-item-narrow {
display:none
}
#content {
border:1px solid #d9dbe2;
border-radius:4px;
margin:1em
}
#content-header {
background-color:#edf0f4;
border-bottom:1px solid #d9dbe2;
border-top-left-radius:3px;
border-top-right-radius:3px;
padding:.7em 1em
}
.type-icon,
.type-icon>* {
width:1.15em
}
.no-linebreak {
white-space:nowrap
}
.ipfs-hash {
color:#7f8491;
font-family:monospace
}
@media only screen and (max-width:500px) {
.menu-item-narrow {
display:inline
}
.menu-item-wide {
display:none
}
}
@media print {
#page-header {
display:none
}
#content-header,
.ipfs-hash,
body {
color:#000
}
#content-header {
border-bottom:1px solid #000
}
#content {
border:1px solid #000
}
a,
a:visited {
color:#000;
text-decoration:underline
}
a[href]:after {
content:" (" attr(href) ")"
}
tr {
page-break-inside:avoid
}
tr:nth-of-type(even) {
background-color:transparent
}
td {
border-top:1px solid #000
}
}
@-ms-viewport {
width:device-width
}
.d-flex {
display:flex
}
.flex-wrap {
flex-flow:wrap
}
.flex-shrink-1 {
flex-shrink:1
}
.ml-auto {
margin-left:auto
}
.table-responsive {
display:block;
width:100%;
overflow-x:auto;
-webkit-overflow-scrolling:touch
}

View File

@ -1,3 +0,0 @@
module github.com/ipfs/dir-index-html/test
go 1.17

View File

@ -1,117 +0,0 @@
package main
import (
"fmt"
"net/http"
"net/url"
"os"
"text/template"
)
const templateFile = "../dir-index.html"
// Copied from go-ipfs/core/corehttp/gateway_indexPage.go
type listingTemplateData struct {
GatewayURL string
DNSLink bool
Listing []directoryItem
Size string
Path string
Breadcrumbs []breadcrumb
BackLink string
Hash string
FastDirIndexThreshold int
}
type directoryItem struct {
Size string
Name string
Path string
Hash string
ShortHash string
}
type breadcrumb struct {
Name string
Path string
}
var testPath = "/ipfs/QmFooBarQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7/a/b/c"
var testData = listingTemplateData{
GatewayURL: "//localhost:3000",
DNSLink: true,
Listing: []directoryItem{{
Size: "25 MiB",
Name: "short-film.mov",
Path: testPath + "/short-film.mov",
Hash: "QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR",
ShortHash: "QmbW\u2026sMnR",
}, {
Size: "23 KiB",
Name: "250pxيوسف_الوزاني_صورة_ملتقطة_بواسطة_مرصد_هابل_الفضائي_توضح_سديم_السرطان،_وهو_بقايا_مستعر_أعظم._.jpg",
Path: testPath + "/250pxيوسف_الوزاني_صورة_ملتقطة_بواسطة_مرصد_هابل_الفضائي_توضح_سديم_السرطان،_وهو_بقايا_مستعر_أعظم._.jpg",
Hash: "QmUwrKrMTrNv8QjWGKMMH5QV9FMPUtRCoQ6zxTdgxATQW6",
ShortHash: "QmUw\u2026TQW6",
}, {
Size: "1 KiB",
Name: "this-piece-of-papers-got-47-words-37-sentences-58-words-we-wanna-know.txt",
Path: testPath + "/this-piece-of-papers-got-47-words-37-sentences-58-words-we-wanna-know.txt",
Hash: "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
ShortHash: "bafy\u2026bzdi",
}},
Size: "25 MiB",
Path: testPath,
Breadcrumbs: []breadcrumb{{
Name: "ipfs",
}, {
Name: "QmFooBarQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7",
Path: testPath + "/../../..",
}, {
Name: "a",
Path: testPath + "/../..",
}, {
Name: "b",
Path: testPath + "/..",
}, {
Name: "c",
Path: testPath,
}},
BackLink: testPath + "/..",
Hash: "QmFooBazBar2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7",
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.Error(w, "Ha-ha, tricked you! There are no files here!", http.StatusNotFound)
return
}
listingTemplate, err := template.New("dir-index.html").Funcs(template.FuncMap{
"iconFromExt": func(name string) string {
return "ipfs-_blank" // place-holder
},
"urlEscape": func(rawUrl string) string {
pathUrl := url.URL{Path: rawUrl}
return pathUrl.String()
},
}).ParseFiles(templateFile)
if err != nil {
http.Error(w, fmt.Sprintf("failed to parse template file: %s", err), http.StatusInternalServerError)
return
}
err = listingTemplate.Execute(w, &testData)
if err != nil {
http.Error(w, fmt.Sprintf("failed to execute template: %s", err), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
})
if _, err := os.Stat(templateFile); err != nil {
wd, _ := os.Getwd()
fmt.Printf("could not open template file %q, relative to %q: %s\n", templateFile, wd, err)
os.Exit(1)
}
fmt.Printf("listening on localhost:3000\n")
http.ListenAndServe("localhost:3000", mux)
}

View File

@ -8,7 +8,7 @@ import (
"os"
"path/filepath"
files "github.com/ipfs/go-ipfs-files"
"github.com/ipfs/go-libipfs/files"
coreiface "github.com/ipfs/interface-go-ipfs-core"
"github.com/ipfs/interface-go-ipfs-core/options"
ipath "github.com/ipfs/interface-go-ipfs-core/path"

View File

@ -30,6 +30,8 @@ import (
fsrepo "github.com/ipfs/kubo/repo/fsrepo"
"github.com/ipfs/kubo/repo/fsrepo/migrations"
"github.com/ipfs/kubo/repo/fsrepo/migrations/ipfsfetcher"
p2pcrypto "github.com/libp2p/go-libp2p/core/crypto"
pnet "github.com/libp2p/go-libp2p/core/pnet"
sockets "github.com/libp2p/go-socket-activation"
cmds "github.com/ipfs/go-ipfs-cmds"
@ -61,6 +63,7 @@ const (
routingOptionNoneKwd = "none"
routingOptionCustomKwd = "custom"
routingOptionDefaultKwd = "default"
routingOptionAutoKwd = "auto"
unencryptTransportKwd = "disable-transport-encryption"
unrestrictedAPIAccessKwd = "unrestricted-api"
writableKwd = "writable"
@ -89,7 +92,7 @@ For example, to change the 'Gateway' port:
ipfs config Addresses.Gateway /ip4/127.0.0.1/tcp/8082
The API address can be changed the same way:
The RPC API address can be changed the same way:
ipfs config Addresses.API /ip4/127.0.0.1/tcp/5002
@ -100,14 +103,14 @@ other computers in the network, use 0.0.0.0 as the ip address:
ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080
Be careful if you expose the API. It is a security risk, as anyone could
Be careful if you expose the RPC API. It is a security risk, as anyone could
control your node remotely. If you need to control the node remotely,
make sure to protect the port as you would other services or database
(firewall, authenticated proxy, etc).
HTTP Headers
ipfs supports passing arbitrary headers to the API and Gateway. You can
ipfs supports passing arbitrary headers to the RPC API and Gateway. You can
do this by setting headers on the API.HTTPHeaders and Gateway.HTTPHeaders
keys:
@ -141,18 +144,6 @@ environment variable:
export IPFS_PATH=/path/to/ipfsrepo
Routing
IPFS by default will use a DHT for content routing. There is an alternative
that operates the DHT in a 'client only' mode that can be enabled by
running the daemon as:
ipfs daemon --routing=dhtclient
Or you can set routing to dhtclient in the config:
ipfs config Routing.Type dhtclient
DEPRECATION NOTICE
Previously, ipfs used an environment variable as seen below:
@ -402,14 +393,30 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment
routingOption, _ := req.Options[routingOptionKwd].(string)
if routingOption == routingOptionDefaultKwd {
routingOption = cfg.Routing.Type
routingOption = cfg.Routing.Type.WithDefault(routingOptionAutoKwd)
if routingOption == "" {
routingOption = routingOptionAutoKwd
}
}
// Private setups can't leverage peers returned by default IPNIs (Routing.Type=auto)
// To avoid breaking existing setups, switch them to DHT-only.
if routingOption == routingOptionAutoKwd {
if key, _ := repo.SwarmKey(); key != nil || pnet.ForcePrivateNetwork {
log.Error("Private networking (swarm.key / LIBP2P_FORCE_PNET) does not work with public HTTP IPNIs enabled by Routing.Type=auto. Kubo will use Routing.Type=dht instead. Update config to remove this message.")
routingOption = routingOptionDHTKwd
}
}
switch routingOption {
case routingOptionSupernodeKwd:
return errors.New("supernode routing was never fully implemented and has been removed")
case routingOptionDefaultKwd, routingOptionAutoKwd:
ncfg.Routing = libp2p.ConstructDefaultRouting(
cfg.Identity.PeerID,
cfg.Addresses.Swarm,
cfg.Identity.PrivKey,
)
case routingOptionDHTClientKwd:
ncfg.Routing = libp2p.DHTClientOption
case routingOptionDHTKwd:
@ -446,8 +453,29 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment
fmt.Printf("Swarm key fingerprint: %x\n", node.PNetFingerprint)
}
if (pnet.ForcePrivateNetwork || node.PNetFingerprint != nil) && routingOption == routingOptionAutoKwd {
// This should never happen, but better safe than sorry
log.Fatal("Private network does not work with Routing.Type=auto. Update your config to Routing.Type=dht (or none, and do manual peering)")
}
printSwarmAddrs(node)
if node.PrivateKey.Type() == p2pcrypto.RSA {
fmt.Print(`
Warning: You are using an RSA Peer ID, which was replaced by Ed25519
as the default recommended in Kubo since September 2020. Signing with
RSA Peer IDs is more CPU-intensive than with other key types.
It is recommended that you change your public key type to ed25519
by using the following command:
ipfs key rotate -o rsa-key-backup -t ed25519
After changing your key type, restart your node for the changes to
take effect.
`)
}
defer func() {
// We wait for the node to close first, as the node has children
// that it will wait for before closing, such as the API server.

View File

@ -19,7 +19,7 @@ import (
fsrepo "github.com/ipfs/kubo/repo/fsrepo"
cmds "github.com/ipfs/go-ipfs-cmds"
files "github.com/ipfs/go-ipfs-files"
"github.com/ipfs/go-libipfs/files"
options "github.com/ipfs/interface-go-ipfs-core/options"
config "github.com/ipfs/kubo/config"
)

View File

@ -12,26 +12,27 @@ import (
"runtime/pprof"
"time"
util "github.com/ipfs/kubo/cmd/ipfs/util"
"github.com/ipfs/kubo/cmd/ipfs/util"
oldcmds "github.com/ipfs/kubo/commands"
core "github.com/ipfs/kubo/core"
"github.com/ipfs/kubo/core"
corecmds "github.com/ipfs/kubo/core/commands"
corehttp "github.com/ipfs/kubo/core/corehttp"
loader "github.com/ipfs/kubo/plugin/loader"
repo "github.com/ipfs/kubo/repo"
fsrepo "github.com/ipfs/kubo/repo/fsrepo"
"github.com/ipfs/kubo/core/corehttp"
"github.com/ipfs/kubo/plugin/loader"
"github.com/ipfs/kubo/repo"
"github.com/ipfs/kubo/repo/fsrepo"
"github.com/ipfs/kubo/tracing"
"go.opentelemetry.io/otel"
cmds "github.com/ipfs/go-ipfs-cmds"
"github.com/ipfs/go-ipfs-cmds/cli"
cmdhttp "github.com/ipfs/go-ipfs-cmds/http"
u "github.com/ipfs/go-ipfs-util"
logging "github.com/ipfs/go-log"
loggables "github.com/libp2p/go-libp2p-loggables"
ma "github.com/multiformats/go-multiaddr"
madns "github.com/multiformats/go-multiaddr-dns"
manet "github.com/multiformats/go-multiaddr/net"
"github.com/google/uuid"
"go.opentelemetry.io/otel"
)
// log is the command logger
@ -77,9 +78,19 @@ func printErr(err error) int {
return 1
}
func newUUID(key string) logging.Metadata {
ids := "#UUID-ERROR#"
if id, err := uuid.NewRandom(); err == nil {
ids = id.String()
}
return logging.Metadata{
key: ids,
}
}
func mainRet() (exitCode int) {
rand.Seed(time.Now().UnixNano())
ctx := logging.ContextWithLoggable(context.Background(), loggables.Uuid("session"))
ctx := logging.ContextWithLoggable(context.Background(), newUUID("session"))
var err error
tp, err := tracing.NewTracerProvider(ctx)
@ -109,7 +120,7 @@ func mainRet() (exitCode int) {
os.Args[1] = "version"
}
//Handle `ipfs help` and `ipfs help <sub-command>`
// Handle `ipfs help` and `ipfs help <sub-command>`
if os.Args[1] == "help" {
if len(os.Args) > 2 {
os.Args = append(os.Args[:1], os.Args[2:]...)

View File

@ -19,7 +19,7 @@ import (
fsrepo "github.com/ipfs/kubo/repo/fsrepo"
fsnotify "github.com/fsnotify/fsnotify"
files "github.com/ipfs/go-ipfs-files"
"github.com/ipfs/go-libipfs/files"
process "github.com/jbenet/goprocess"
homedir "github.com/mitchellh/go-homedir"
)

View File

@ -4,6 +4,7 @@ codecov:
- "!travis-ci.org"
- "!ci.ipfs.team:8111"
- "!ci.ipfs.team"
- "!github.com"
notify:
require_ci_to_pass: no
after_n_builds: 2

View File

@ -45,14 +45,6 @@ type Gateway struct {
// PathPrefixes was removed: https://github.com/ipfs/go-ipfs/issues/7702
PathPrefixes []string
// FastDirIndexThreshold is the maximum number of items in a directory
// before the Gateway switches to a shallow, faster listing which only
// requires the root node. This allows for listing big directories fast,
// without the linear slowdown caused by reading size metadata from child
// nodes.
// Setting to 0 will enable fast listings for all directories.
FastDirIndexThreshold *OptionalInteger `json:",omitempty"`
// FIXME: Not yet implemented: https://github.com/ipfs/kubo/issues/8059
APICommands []string

View File

@ -48,7 +48,7 @@ func InitWithIdentity(identity Identity) (*Config, error) {
},
Routing: Routing{
Type: "dht",
Type: nil,
Methods: nil,
Routers: nil,
},
@ -76,8 +76,8 @@ func InitWithIdentity(identity Identity) (*Config, error) {
APICommands: []string{},
},
Reprovider: Reprovider{
Interval: "12h",
Strategy: "all",
Interval: nil,
Strategy: nil,
},
Pinning: Pinning{
RemoteServices: map[string]RemotePinningService{},
@ -96,11 +96,11 @@ func InitWithIdentity(identity Identity) (*Config, error) {
// DefaultConnMgrHighWater is the default value for the connection managers
// 'high water' mark
const DefaultConnMgrHighWater = 150
const DefaultConnMgrHighWater = 96
// DefaultConnMgrLowWater is the default value for the connection managers 'low
// water' mark
const DefaultConnMgrLowWater = 50
const DefaultConnMgrLowWater = 32
// DefaultConnMgrGracePeriod is the default value for the connection managers
// grace period
@ -110,13 +110,21 @@ const DefaultConnMgrGracePeriod = time.Second * 20
// type.
const DefaultConnMgrType = "basic"
// DefaultResourceMgrMinInboundConns is a MAGIC number that probably a good
// enough number of inbound conns to be a good network citizen.
const DefaultResourceMgrMinInboundConns = 800
func addressesConfig() Addresses {
return Addresses{
Swarm: []string{
"/ip4/0.0.0.0/tcp/4001",
"/ip6/::/tcp/4001",
"/ip4/0.0.0.0/udp/4001/quic",
"/ip4/0.0.0.0/udp/4001/quic-v1",
"/ip4/0.0.0.0/udp/4001/quic-v1/webtransport",
"/ip6/::/udp/4001/quic",
"/ip6/::/udp/4001/quic-v1",
"/ip6/::/udp/4001/quic-v1/webtransport",
},
Announce: []string{},
AppendAnnounce: []string{},

View File

@ -174,9 +174,9 @@ functionality - performance of content discovery and data
fetching may be degraded.
`,
Transform: func(c *Config) error {
c.Routing.Type = "dhtclient"
c.Routing.Type = NewOptionalString("dhtclient") // TODO: https://github.com/ipfs/kubo/issues/9480
c.AutoNAT.ServiceMode = AutoNATServiceDisabled
c.Reprovider.Interval = "0"
c.Reprovider.Interval = NewOptionalDuration(0)
lowWater := int64(20)
highWater := int64(40)

View File

@ -1,5 +1,24 @@
package config
const (
// LastSeenMessagesStrategy is a strategy that calculates the TTL countdown
// based on the last time a Pubsub message is seen. This means that if a message
// is received and then seen again within the specified TTL window, it
// won't be emitted until the TTL countdown expires from the last time the
// message was seen.
LastSeenMessagesStrategy = "last-seen"
// FirstSeenMessagesStrategy is a strategy that calculates the TTL
// countdown based on the first time a Pubsub message is seen. This means that if
// a message is received and then seen again within the specified TTL
// window, it won't be emitted.
FirstSeenMessagesStrategy = "first-seen"
// DefaultSeenMessagesStrategy is the strategy that is used by default if
// no Pubsub.SeenMessagesStrategy is specified.
DefaultSeenMessagesStrategy = LastSeenMessagesStrategy
)
type PubsubConfig struct {
// Router can be either floodsub (legacy) or gossipsub (new and
// backwards compatible).
@ -12,7 +31,11 @@ type PubsubConfig struct {
// Enable pubsub (--enable-pubsub-experiment)
Enabled Flag `json:",omitempty"`
// SeenMessagesTTL configures the duration after which a previously seen
// message ID can be forgotten about.
// SeenMessagesTTL is a value that controls the time window within which
// duplicate messages will be identified and won't be emitted.
SeenMessagesTTL *OptionalDuration `json:",omitempty"`
// SeenMessagesStrategy is a setting that determines how the time-to-live
// (TTL) countdown for deduplicating messages is calculated.
SeenMessagesStrategy *OptionalString `json:",omitempty"`
}

View File

@ -1,6 +1,11 @@
package config
import "time"
const DefaultReproviderInterval = time.Hour * 22 // https://github.com/ipfs/kubo/pull/9326
const DefaultReproviderStrategy = "all"
type Reprovider struct {
Interval string // Time period to reprovide locally stored objects to the network
Strategy string // Which keys to announce
Interval *OptionalDuration `json:",omitempty"` // Time period to reprovide locally stored objects to the network
Strategy *OptionalString `json:",omitempty"` // Which keys to announce
}

View File

@ -10,9 +10,10 @@ import (
type Routing struct {
// Type sets default daemon routing mode.
//
// Can be one of "dht", "dhtclient", "dhtserver", "none", or "custom".
// When "custom" is set, you can specify a list of Routers.
Type string
// Can be one of "auto", "dht", "dhtclient", "dhtserver", "none", or "custom".
// When unset or set to "auto", DHT and implicit routers are used.
// When "custom" is set, user-provided Routing.Routers is used.
Type *OptionalString `json:",omitempty"`
Routers Routers
@ -21,10 +22,7 @@ type Routing struct {
type Router struct {
// Currenly supported Types are "reframe", "dht", "parallel", "sequential".
// Reframe type allows to add other resolvers using the Reframe spec:
// https://github.com/ipfs/specs/tree/main/reframe
// In the future we will support "dht" and other Types here.
// Router type ID. See RouterType for more info.
Type RouterType
// Parameters are extra configuration that this router might need.
@ -106,11 +104,11 @@ func (r *RouterParser) UnmarshalJSON(b []byte) error {
type RouterType string
const (
RouterTypeReframe RouterType = "reframe"
RouterTypeHTTP RouterType = "http"
RouterTypeDHT RouterType = "dht"
RouterTypeSequential RouterType = "sequential"
RouterTypeParallel RouterType = "parallel"
RouterTypeReframe RouterType = "reframe" // More info here: https://github.com/ipfs/specs/tree/main/reframe . Actually deprecated.
RouterTypeHTTP RouterType = "http" // HTTP JSON API for delegated routing systems (IPIP-337).
RouterTypeDHT RouterType = "dht" // DHT router.
RouterTypeSequential RouterType = "sequential" // Router helper to execute several routers sequentially.
RouterTypeParallel RouterType = "parallel" // Router helper to execute several routers in parallel.
)
type DHTMode string

View File

@ -13,7 +13,7 @@ func TestRouterParameters(t *testing.T) {
sec := time.Second
min := time.Minute
r := Routing{
Type: "custom",
Type: NewOptionalString("custom"),
Routers: map[string]RouterParser{
"router-dht": {Router{
Type: RouterTypeDHT,
@ -113,7 +113,7 @@ func TestRouterMissingParameters(t *testing.T) {
require := require.New(t)
r := Routing{
Type: "custom",
Type: NewOptionalString("custom"),
Routers: map[string]RouterParser{
"router-wrong-reframe": {Router{
Type: RouterTypeReframe,

View File

@ -218,6 +218,11 @@ type OptionalDuration struct {
value *time.Duration
}
// NewOptionalDuration returns an OptionalDuration from a string
func NewOptionalDuration(d time.Duration) *OptionalDuration {
return &OptionalDuration{value: &d}
}
func (d *OptionalDuration) UnmarshalJSON(input []byte) error {
switch string(input) {
case "null", "undefined", "\"null\"", "", "default", "\"\"", "\"default\"":

View File

@ -84,6 +84,7 @@ func NewNode(ctx context.Context, cfg *BuildCfg) (*IpfsNode, error) {
return nil, fmt.Errorf("building fx opts: %w", err)
}
}
//nolint:staticcheck // https://github.com/ipfs/kubo/pull/9423#issuecomment-1341038770
opts = append(opts, fx.Extract(n))
app := fx.New(opts...)

View File

@ -12,8 +12,8 @@ import (
"github.com/cheggaaa/pb"
cmds "github.com/ipfs/go-ipfs-cmds"
files "github.com/ipfs/go-ipfs-files"
ipld "github.com/ipfs/go-ipld-format"
"github.com/ipfs/go-libipfs/files"
mfs "github.com/ipfs/go-mfs"
coreiface "github.com/ipfs/interface-go-ipfs-core"
"github.com/ipfs/interface-go-ipfs-core/options"

View File

@ -8,10 +8,10 @@ import (
e "github.com/ipfs/kubo/core/commands/e"
humanize "github.com/dustin/go-humanize"
bitswap "github.com/ipfs/go-bitswap"
decision "github.com/ipfs/go-bitswap/decision"
cidutil "github.com/ipfs/go-cidutil"
cmds "github.com/ipfs/go-ipfs-cmds"
bitswap "github.com/ipfs/go-libipfs/bitswap"
decision "github.com/ipfs/go-libipfs/bitswap/decision"
peer "github.com/libp2p/go-libp2p/core/peer"
)

View File

@ -6,7 +6,7 @@ import (
"io"
"os"
files "github.com/ipfs/go-ipfs-files"
"github.com/ipfs/go-libipfs/files"
cmdenv "github.com/ipfs/kubo/core/commands/cmdenv"
"github.com/ipfs/kubo/core/commands/cmdutils"

View File

@ -9,9 +9,9 @@ import (
"github.com/ipfs/kubo/core/commands/cmdenv"
"github.com/cheggaaa/pb"
"github.com/ipfs/go-ipfs-cmds"
"github.com/ipfs/go-ipfs-files"
"github.com/ipfs/interface-go-ipfs-core"
cmds "github.com/ipfs/go-ipfs-cmds"
"github.com/ipfs/go-libipfs/files"
iface "github.com/ipfs/interface-go-ipfs-core"
"github.com/ipfs/interface-go-ipfs-core/path"
)

View File

@ -3,7 +3,7 @@ package cmdenv
import (
"fmt"
files "github.com/ipfs/go-ipfs-files"
"github.com/ipfs/go-libipfs/files"
)
// GetFileArg returns the next file from the directory or an error

View File

@ -173,6 +173,7 @@ func TestCommands(t *testing.T) {
"/multibase/transcode",
"/multibase/list",
"/name",
"/name/inspect",
"/name/publish",
"/name/pubsub",
"/name/pubsub/cancel",

View File

@ -9,9 +9,9 @@ import (
"time"
"github.com/cheggaaa/pb"
blocks "github.com/ipfs/go-block-format"
cid "github.com/ipfs/go-cid"
ipld "github.com/ipfs/go-ipld-format"
blocks "github.com/ipfs/go-libipfs/blocks"
iface "github.com/ipfs/interface-go-ipfs-core"
"github.com/ipfs/kubo/core/commands/cmdenv"

View File

@ -6,9 +6,9 @@ import (
"io"
cid "github.com/ipfs/go-cid"
files "github.com/ipfs/go-ipfs-files"
ipld "github.com/ipfs/go-ipld-format"
ipldlegacy "github.com/ipfs/go-ipld-legacy"
"github.com/ipfs/go-libipfs/files"
iface "github.com/ipfs/interface-go-ipfs-core"
"github.com/ipfs/interface-go-ipfs-core/options"
"github.com/ipfs/kubo/core/commands/cmdenv"

View File

@ -4,17 +4,17 @@ import (
"bytes"
"fmt"
blocks "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
ipldlegacy "github.com/ipfs/go-ipld-legacy"
blocks "github.com/ipfs/go-libipfs/blocks"
"github.com/ipfs/kubo/core/commands/cmdenv"
"github.com/ipfs/kubo/core/commands/cmdutils"
"github.com/ipld/go-ipld-prime/multicodec"
basicnode "github.com/ipld/go-ipld-prime/node/basic"
cmds "github.com/ipfs/go-ipfs-cmds"
files "github.com/ipfs/go-ipfs-files"
ipld "github.com/ipfs/go-ipld-format"
"github.com/ipfs/go-libipfs/files"
mc "github.com/multiformats/go-multicodec"
// Expected minimal set of available format/ienc codecs.

View File

@ -16,7 +16,7 @@ import (
"github.com/cheggaaa/pb"
cmds "github.com/ipfs/go-ipfs-cmds"
files "github.com/ipfs/go-ipfs-files"
"github.com/ipfs/go-libipfs/files"
"github.com/ipfs/go-libipfs/tar"
"github.com/ipfs/interface-go-ipfs-core/path"
)

View File

@ -1,7 +1,25 @@
package name
import (
"github.com/ipfs/go-ipfs-cmds"
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
"text/tabwriter"
"time"
"github.com/gogo/protobuf/proto"
cmds "github.com/ipfs/go-ipfs-cmds"
"github.com/ipfs/go-ipns"
ipns_pb "github.com/ipfs/go-ipns/pb"
cmdenv "github.com/ipfs/kubo/core/commands/cmdenv"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagcbor"
"github.com/ipld/go-ipld-prime/codec/dagjson"
ic "github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
mbase "github.com/multiformats/go-multibase"
)
type IpnsEntry struct {
@ -62,5 +80,204 @@ Resolve the value of a dnslink:
"publish": PublishCmd,
"resolve": IpnsCmd,
"pubsub": IpnsPubsubCmd,
"inspect": IpnsInspectCmd,
},
}
type IpnsInspectValidation struct {
Valid bool
Reason string
PublicKey peer.ID
}
// IpnsInspectEntry contains the deserialized values from an IPNS Entry:
// https://github.com/ipfs/specs/blob/main/ipns/IPNS.md#record-serialization-format
type IpnsInspectEntry struct {
Value string
ValidityType *ipns_pb.IpnsEntry_ValidityType
Validity *time.Time
Sequence uint64
TTL *uint64
PublicKey string
SignatureV1 string
SignatureV2 string
Data interface{}
}
type IpnsInspectResult struct {
Entry IpnsInspectEntry
Validation *IpnsInspectValidation
}
var IpnsInspectCmd = &cmds.Command{
Status: cmds.Experimental,
Helptext: cmds.HelpText{
Tagline: "Inspects an IPNS Record",
ShortDescription: `
Prints values inside of IPNS Record protobuf and its DAG-CBOR Data field.
Passing --verify will verify signature against provided public key.
`,
LongDescription: `
Prints values inside of IPNS Record protobuf and its DAG-CBOR Data field.
The input can be a file or STDIN, the output can be JSON:
$ ipfs routing get "/ipns/$PEERID" > ipns_record
$ ipfs name inspect --enc=json < ipns_record
Values in PublicKey, SignatureV1 and SignatureV2 fields are raw bytes encoded
in Multibase. The Data field is DAG-CBOR represented as DAG-JSON.
Passing --verify will verify signature against provided public key.
`,
},
Arguments: []cmds.Argument{
cmds.FileArg("record", true, false, "The IPNS record payload to be verified.").EnableStdin(),
},
Options: []cmds.Option{
cmds.StringOption("verify", "CID of the public IPNS key to validate against."),
},
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
file, err := cmdenv.GetFileArg(req.Files.Entries())
if err != nil {
return err
}
defer file.Close()
var b bytes.Buffer
_, err = io.Copy(&b, file)
if err != nil {
return err
}
var entry ipns_pb.IpnsEntry
err = proto.Unmarshal(b.Bytes(), &entry)
if err != nil {
return err
}
encoder, err := mbase.EncoderByName("base64")
if err != nil {
return err
}
result := &IpnsInspectResult{
Entry: IpnsInspectEntry{
Value: string(entry.Value),
ValidityType: entry.ValidityType,
Sequence: *entry.Sequence,
TTL: entry.Ttl,
PublicKey: encoder.Encode(entry.PubKey),
SignatureV1: encoder.Encode(entry.SignatureV1),
SignatureV2: encoder.Encode(entry.SignatureV2),
Data: nil,
},
}
if len(entry.Data) != 0 {
// This is hacky. The variable node (datamodel.Node) doesn't directly marshal
// to JSON. Therefore, we need to first decode from DAG-CBOR, then encode in
// DAG-JSON and finally unmarshal it from JSON. Since DAG-JSON is a subset
// of JSON, that should work. Then, we can store the final value in the
// result.Entry.Data for further inspection.
node, err := ipld.Decode(entry.Data, dagcbor.Decode)
if err != nil {
return err
}
var buf bytes.Buffer
err = dagjson.Encode(node, &buf)
if err != nil {
return err
}
err = json.Unmarshal(buf.Bytes(), &result.Entry.Data)
if err != nil {
return err
}
}
validity, err := ipns.GetEOL(&entry)
if err == nil {
result.Entry.Validity = &validity
}
verify, ok := req.Options["verify"].(string)
if ok {
key := strings.TrimPrefix(verify, "/ipns/")
id, err := peer.Decode(key)
if err != nil {
return err
}
result.Validation = &IpnsInspectValidation{
PublicKey: id,
}
pub, err := id.ExtractPublicKey()
if err != nil {
// Make sure it works with all those RSA that cannot be embedded into the
// Peer ID.
if len(entry.PubKey) > 0 {
pub, err = ic.UnmarshalPublicKey(entry.PubKey)
}
}
if err != nil {
return err
}
err = ipns.Validate(pub, &entry)
if err == nil {
result.Validation.Valid = true
} else {
result.Validation.Reason = err.Error()
}
}
return cmds.EmitOnce(res, result)
},
Type: IpnsInspectResult{},
Encoders: cmds.EncoderMap{
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *IpnsInspectResult) error {
tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0)
defer tw.Flush()
fmt.Fprintf(tw, "Value:\t%q\n", string(out.Entry.Value))
fmt.Fprintf(tw, "Validity Type:\t%q\n", out.Entry.ValidityType)
if out.Entry.Validity != nil {
fmt.Fprintf(tw, "Validity:\t%s\n", out.Entry.Validity.Format(time.RFC3339Nano))
}
fmt.Fprintf(tw, "Sequence:\t%d\n", out.Entry.Sequence)
if out.Entry.TTL != nil {
fmt.Fprintf(tw, "TTL:\t%d\n", *out.Entry.TTL)
}
fmt.Fprintf(tw, "PublicKey:\t%q\n", out.Entry.PublicKey)
fmt.Fprintf(tw, "Signature V1:\t%q\n", out.Entry.SignatureV1)
fmt.Fprintf(tw, "Signature V2:\t%q\n", out.Entry.SignatureV2)
data, err := json.Marshal(out.Entry.Data)
if err != nil {
return err
}
fmt.Fprintf(tw, "Data:\t%s\n", string(data))
if out.Validation == nil {
tw.Flush()
fmt.Fprintf(w, "\nThis record was not validated.\n")
} else {
tw.Flush()
fmt.Fprintf(w, "\nValidation results:\n")
fmt.Fprintf(tw, "\tValid:\t%v\n", out.Validation.Valid)
if out.Validation.Reason != "" {
fmt.Fprintf(tw, "\tReason:\t%s\n", out.Validation.Reason)
}
fmt.Fprintf(tw, "\tPublicKey:\t%s\n", out.Validation.PublicKey)
}
return nil
}),
},
}

View File

@ -2,7 +2,6 @@ package commands
import (
"context"
"encoding/base64"
"errors"
"fmt"
"io"
@ -135,6 +134,7 @@ const (
)
var provideRefRoutingCmd = &cmds.Command{
Status: cmds.Experimental,
Helptext: cmds.HelpText{
Tagline: "Announce to the network that you are providing given values.",
},
@ -346,6 +346,7 @@ var findPeerRoutingCmd = &cmds.Command{
}
var getValueRoutingCmd = &cmds.Command{
Status: cmds.Experimental,
Helptext: cmds.HelpText{
Tagline: "Given a key, query the routing system for its best value.",
ShortDescription: `
@ -362,78 +363,30 @@ Different key types can specify other 'best' rules.
Arguments: []cmds.Argument{
cmds.StringArg("key", true, true, "The key to find a value for."),
},
Options: []cmds.Option{
cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
},
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
nd, err := cmdenv.GetNode(env)
api, err := cmdenv.GetApi(env, req)
if err != nil {
return err
}
if !nd.IsOnline {
return ErrNotOnline
}
dhtkey, err := escapeDhtKey(req.Arguments[0])
r, err := api.Routing().Get(req.Context, req.Arguments[0])
if err != nil {
return err
}
ctx, cancel := context.WithCancel(req.Context)
ctx, events := routing.RegisterForQueryEvents(ctx)
var getErr error
go func() {
defer cancel()
var val []byte
val, getErr = nd.Routing.GetValue(ctx, dhtkey)
if getErr != nil {
routing.PublishQueryEvent(ctx, &routing.QueryEvent{
Type: routing.QueryError,
Extra: getErr.Error(),
})
} else {
routing.PublishQueryEvent(ctx, &routing.QueryEvent{
Type: routing.Value,
Extra: base64.StdEncoding.EncodeToString(val),
})
}
}()
for e := range events {
if err := res.Emit(e); err != nil {
return err
}
}
return getErr
return res.Emit(r)
},
Encoders: cmds.EncoderMap{
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
pfm := pfuncMap{
routing.Value: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
if verbose {
_, err := fmt.Fprintf(out, "got value: '%s'\n", obj.Extra)
return err
}
res, err := base64.StdEncoding.DecodeString(obj.Extra)
if err != nil {
return err
}
_, err = out.Write(res)
return err
},
}
verbose, _ := req.Options[dhtVerboseOptionName].(bool)
return printEvent(out, w, verbose, pfm)
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out []byte) error {
_, err := w.Write(out)
return err
}),
},
Type: routing.QueryEvent{},
Type: []byte{},
}
var putValueRoutingCmd = &cmds.Command{
Status: cmds.Experimental,
Helptext: cmds.HelpText{
Tagline: "Write a key/value pair to the routing system.",
ShortDescription: `
@ -459,20 +412,8 @@ identified by QmFoo.
cmds.StringArg("key", true, false, "The key to store the value at."),
cmds.FileArg("value-file", true, false, "A path to a file containing the value to store.").EnableStdin(),
},
Options: []cmds.Option{
cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
},
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
nd, err := cmdenv.GetNode(env)
if err != nil {
return err
}
if !nd.IsOnline {
return ErrNotOnline
}
key, err := escapeDhtKey(req.Arguments[0])
api, err := cmdenv.GetApi(env, req)
if err != nil {
return err
}
@ -488,50 +429,20 @@ identified by QmFoo.
return err
}
ctx, cancel := context.WithCancel(req.Context)
ctx, events := routing.RegisterForQueryEvents(ctx)
var putErr error
go func() {
defer cancel()
putErr = nd.Routing.PutValue(ctx, key, []byte(data))
if putErr != nil {
routing.PublishQueryEvent(ctx, &routing.QueryEvent{
Type: routing.QueryError,
Extra: putErr.Error(),
})
}
}()
for e := range events {
if err := res.Emit(e); err != nil {
return err
}
err = api.Routing().Put(req.Context, req.Arguments[0], data)
if err != nil {
return err
}
return putErr
return res.Emit([]byte(fmt.Sprintf("%s added", req.Arguments[0])))
},
Encoders: cmds.EncoderMap{
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
pfm := pfuncMap{
routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
if verbose {
fmt.Fprintf(out, "* closest peer %s\n", obj.ID)
}
return nil
},
routing.Value: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
fmt.Fprintf(out, "%s\n", obj.ID.Pretty())
return nil
},
}
verbose, _ := req.Options[dhtVerboseOptionName].(bool)
return printEvent(out, w, verbose, pfm)
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out []byte) error {
_, err := w.Write(out)
return err
}),
},
Type: routing.QueryEvent{},
Type: []byte{},
}
type printFunc func(obj *routing.QueryEvent, out io.Writer, verbose bool) error

View File

@ -12,7 +12,7 @@ import (
"sync"
"time"
files "github.com/ipfs/go-ipfs-files"
"github.com/ipfs/go-libipfs/files"
"github.com/ipfs/kubo/commands"
"github.com/ipfs/kubo/config"
"github.com/ipfs/kubo/core/commands/cmdenv"
@ -337,12 +337,15 @@ The scope can be one of the following:
- all -- reports the resource usage for all currently active scopes.
The output of this command is JSON.
To see all resources that are close to hitting their respective limit, one can do something like:
ipfs swarm stats --min-used-limit-perc=90 all
`},
Arguments: []cmds.Argument{
cmds.StringArg("scope", true, false, "scope of the stat report"),
},
Options: []cmds.Option{
cmds.IntOption(swarmUsedResourcesPercentageName, "Display only resources that are using above the specified percentage"),
cmds.IntOption(swarmUsedResourcesPercentageName, "Only display resources that are using above the specified percentage of their respective limit"),
},
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
node, err := cmdenv.GetNode(env)

View File

@ -9,7 +9,7 @@ import (
cmdenv "github.com/ipfs/kubo/core/commands/cmdenv"
cmds "github.com/ipfs/go-ipfs-cmds"
files "github.com/ipfs/go-ipfs-files"
"github.com/ipfs/go-libipfs/files"
"github.com/ipfs/interface-go-ipfs-core/options"
)

View File

@ -221,7 +221,7 @@ func GetNode(t *testing.T, reframeURLs ...string) *IpfsNode {
API: []string{"/ip4/127.0.0.1/tcp/0"},
},
Routing: config.Routing{
Type: "custom",
Type: config.NewOptionalString("custom"),
Routers: routers,
Methods: config.Methods{
config.MethodNameFindPeers: config.Method{

View File

@ -6,9 +6,9 @@ import (
"errors"
"io"
blocks "github.com/ipfs/go-block-format"
cid "github.com/ipfs/go-cid"
pin "github.com/ipfs/go-ipfs-pinner"
blocks "github.com/ipfs/go-libipfs/blocks"
coreiface "github.com/ipfs/interface-go-ipfs-core"
caopts "github.com/ipfs/interface-go-ipfs-core/options"
path "github.com/ipfs/interface-go-ipfs-core/path"

View File

@ -144,6 +144,11 @@ func (api *CoreAPI) PubSub() coreiface.PubSubAPI {
return (*PubSubAPI)(api)
}
// Routing returns the RoutingAPI interface implementation backed by the kubo node
func (api *CoreAPI) Routing() coreiface.RoutingAPI {
return (*RoutingAPI)(api)
}
// WithOptions returns api with global options applied
func (api *CoreAPI) WithOptions(opts ...options.ApiOption) (coreiface.CoreAPI, error) {
settings := api.parentOpts // make sure to copy

View File

@ -15,6 +15,7 @@ import (
ipath "github.com/ipfs/go-path"
coreiface "github.com/ipfs/interface-go-ipfs-core"
caopts "github.com/ipfs/interface-go-ipfs-core/options"
nsopts "github.com/ipfs/interface-go-ipfs-core/options/namesys"
path "github.com/ipfs/interface-go-ipfs-core/path"
ci "github.com/libp2p/go-libp2p/core/crypto"
peer "github.com/libp2p/go-libp2p/core/peer"
@ -37,8 +38,6 @@ func (e *ipnsEntry) Value() path.Path {
return e.value
}
type requestContextKey string
// Publish announces new IPNS name and returns the new IPNS entry.
func (api *NameAPI) Publish(ctx context.Context, p path.Path, opts ...caopts.NamePublishOption) (coreiface.IpnsEntry, error) {
ctx, span := tracing.Span(ctx, "CoreAPI.NameAPI", "Publish", trace.WithAttributes(attribute.String("path", p.String())))
@ -76,13 +75,17 @@ func (api *NameAPI) Publish(ctx context.Context, p path.Path, opts ...caopts.Nam
return nil, err
}
if options.TTL != nil {
// nolint: staticcheck // non-backward compatible change
ctx = context.WithValue(ctx, requestContextKey("ipns-publish-ttl"), *options.TTL)
eol := time.Now().Add(options.ValidTime)
publishOptions := []nsopts.PublishOption{
nsopts.PublishWithEOL(eol),
}
eol := time.Now().Add(options.ValidTime)
err = api.namesys.PublishWithEOL(ctx, k, pth, eol)
if options.TTL != nil {
publishOptions = append(publishOptions, nsopts.PublishWithTTL(*options.TTL))
}
err = api.namesys.Publish(ctx, k, pth, publishOptions...)
if err != nil {
return nil, err
}

53
core/coreapi/routing.go Normal file
View File

@ -0,0 +1,53 @@
package coreapi
import (
"context"
"errors"
"github.com/ipfs/go-path"
coreiface "github.com/ipfs/interface-go-ipfs-core"
peer "github.com/libp2p/go-libp2p/core/peer"
)
type RoutingAPI CoreAPI
func (r *RoutingAPI) Get(ctx context.Context, key string) ([]byte, error) {
if !r.nd.IsOnline {
return nil, coreiface.ErrOffline
}
dhtKey, err := normalizeKey(key)
if err != nil {
return nil, err
}
return r.routing.GetValue(ctx, dhtKey)
}
func (r *RoutingAPI) Put(ctx context.Context, key string, value []byte) error {
if !r.nd.IsOnline {
return coreiface.ErrOffline
}
dhtKey, err := normalizeKey(key)
if err != nil {
return err
}
return r.routing.PutValue(ctx, dhtKey, value)
}
func normalizeKey(s string) (string, error) {
parts := path.SplitList(s)
if len(parts) != 3 ||
parts[0] != "" ||
!(parts[1] == "ipns" || parts[1] == "pk") {
return "", errors.New("invalid key")
}
k, err := peer.Decode(parts[2])
if err != nil {
return "", err
}
return path.Join(append(parts[:2], string(k))), nil
}

View File

@ -6,7 +6,7 @@ import (
"testing"
"time"
files "github.com/ipfs/go-ipfs-files"
"github.com/ipfs/go-libipfs/files"
"github.com/ipfs/go-merkledag"
uio "github.com/ipfs/go-unixfs/io"
"github.com/ipfs/interface-go-ipfs-core/options"

View File

@ -17,8 +17,8 @@ import (
cidutil "github.com/ipfs/go-cidutil"
filestore "github.com/ipfs/go-filestore"
bstore "github.com/ipfs/go-ipfs-blockstore"
files "github.com/ipfs/go-ipfs-files"
ipld "github.com/ipfs/go-ipld-format"
"github.com/ipfs/go-libipfs/files"
merkledag "github.com/ipfs/go-merkledag"
dagtest "github.com/ipfs/go-merkledag/test"
mfs "github.com/ipfs/go-mfs"
@ -271,32 +271,36 @@ func (api *UnixfsAPI) processLink(ctx context.Context, linkres ft.LinkResult, se
lnk.Type = coreiface.TFile
lnk.Size = linkres.Link.Size
case cid.DagProtobuf:
if !settings.ResolveChildren {
break
}
linkNode, err := linkres.Link.GetNode(ctx, api.dag)
if err != nil {
lnk.Err = err
break
}
if pn, ok := linkNode.(*merkledag.ProtoNode); ok {
d, err := ft.FSNodeFromBytes(pn.Data())
if settings.ResolveChildren {
linkNode, err := linkres.Link.GetNode(ctx, api.dag)
if err != nil {
lnk.Err = err
break
}
switch d.Type() {
case ft.TFile, ft.TRaw:
lnk.Type = coreiface.TFile
case ft.THAMTShard, ft.TDirectory, ft.TMetadata:
lnk.Type = coreiface.TDirectory
case ft.TSymlink:
lnk.Type = coreiface.TSymlink
lnk.Target = string(d.Data())
if pn, ok := linkNode.(*merkledag.ProtoNode); ok {
d, err := ft.FSNodeFromBytes(pn.Data())
if err != nil {
lnk.Err = err
break
}
switch d.Type() {
case ft.TFile, ft.TRaw:
lnk.Type = coreiface.TFile
case ft.THAMTShard, ft.TDirectory, ft.TMetadata:
lnk.Type = coreiface.TDirectory
case ft.TSymlink:
lnk.Type = coreiface.TSymlink
lnk.Target = string(d.Data())
}
if !settings.UseCumulativeSize {
lnk.Size = d.FileSize()
}
}
lnk.Size = d.FileSize()
}
if settings.UseCumulativeSize {
lnk.Size = linkres.Link.Size
}
}

View File

@ -1,15 +1,12 @@
package corehttp
import (
"context"
"fmt"
"net"
"net/http"
"sort"
coreiface "github.com/ipfs/interface-go-ipfs-core"
"github.com/ipfs/go-libipfs/gateway"
options "github.com/ipfs/interface-go-ipfs-core/options"
path "github.com/ipfs/interface-go-ipfs-core/path"
version "github.com/ipfs/kubo"
core "github.com/ipfs/kubo/core"
coreapi "github.com/ipfs/kubo/core/coreapi"
@ -17,47 +14,6 @@ import (
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
type GatewayConfig struct {
Headers map[string][]string
Writable bool
FastDirIndexThreshold int
}
// NodeAPI defines the minimal set of API services required by a gateway handler
type NodeAPI interface {
// Unixfs returns an implementation of Unixfs API
Unixfs() coreiface.UnixfsAPI
// Block returns an implementation of Block API
Block() coreiface.BlockAPI
// Dag returns an implementation of Dag API
Dag() coreiface.APIDagService
// ResolvePath resolves the path using Unixfs resolver
ResolvePath(context.Context, path.Path) (path.Resolved, error)
}
// A helper function to clean up a set of headers:
// 1. Canonicalizes.
// 2. Deduplicates.
// 3. Sorts.
func cleanHeaderSet(headers []string) []string {
// Deduplicate and canonicalize.
m := make(map[string]struct{}, len(headers))
for _, h := range headers {
m[http.CanonicalHeaderKey(h)] = struct{}{}
}
result := make([]string, 0, len(m))
for k := range m {
result = append(result, k)
}
// Sort
sort.Strings(result)
return result
}
func GatewayOption(writable bool, paths ...string) ServeOption {
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
cfg, err := n.Repo.Config()
@ -75,17 +31,16 @@ func GatewayOption(writable bool, paths ...string) ServeOption {
headers[http.CanonicalHeaderKey(h)] = v
}
AddAccessControlHeaders(headers)
gateway.AddAccessControlHeaders(headers)
offlineAPI, err := api.WithOptions(options.Api.Offline(true))
if err != nil {
return nil, err
}
gateway := NewGatewayHandler(GatewayConfig{
Headers: headers,
Writable: writable,
FastDirIndexThreshold: int(cfg.Gateway.FastDirIndexThreshold.WithDefault(100)),
gateway := gateway.NewHandler(gateway.Config{
Headers: headers,
Writable: writable,
}, api, offlineAPI)
gateway = otelhttp.NewHandler(gateway, "Gateway.Request")
@ -97,50 +52,6 @@ func GatewayOption(writable bool, paths ...string) ServeOption {
}
}
// AddAccessControlHeaders adds default headers used for controlling
// cross-origin requests. This function adds several values to the
// Access-Control-Allow-Headers and Access-Control-Expose-Headers entries.
// If the Access-Control-Allow-Origin entry is missing a value of '*' is
// added, indicating that browsers should allow requesting code from any
// origin to access the resource.
// If the Access-Control-Allow-Methods entry is missing a value of 'GET' is
// added, indicating that browsers may use the GET method when issuing cross
// origin requests.
func AddAccessControlHeaders(headers map[string][]string) {
// Hard-coded headers.
const ACAHeadersName = "Access-Control-Allow-Headers"
const ACEHeadersName = "Access-Control-Expose-Headers"
const ACAOriginName = "Access-Control-Allow-Origin"
const ACAMethodsName = "Access-Control-Allow-Methods"
if _, ok := headers[ACAOriginName]; !ok {
// Default to *all*
headers[ACAOriginName] = []string{"*"}
}
if _, ok := headers[ACAMethodsName]; !ok {
// Default to GET
headers[ACAMethodsName] = []string{http.MethodGet}
}
headers[ACAHeadersName] = cleanHeaderSet(
append([]string{
"Content-Type",
"User-Agent",
"Range",
"X-Requested-With",
}, headers[ACAHeadersName]...))
headers[ACEHeadersName] = cleanHeaderSet(
append([]string{
"Content-Length",
"Content-Range",
"X-Chunked-Output",
"X-Stream-Output",
"X-Ipfs-Path",
"X-Ipfs-Roots",
}, headers[ACEHeadersName]...))
}
func VersionOption() ServeOption {
return func(_ *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {

File diff suppressed because it is too large Load Diff

View File

@ -1,55 +0,0 @@
package corehttp
import (
"bytes"
"context"
"io"
"net/http"
"time"
ipath "github.com/ipfs/interface-go-ipfs-core/path"
"github.com/ipfs/kubo/tracing"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// serveRawBlock returns bytes behind a raw block
func (i *gatewayHandler) serveRawBlock(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, begin time.Time) {
ctx, span := tracing.Span(ctx, "Gateway", "ServeRawBlock", trace.WithAttributes(attribute.String("path", resolvedPath.String())))
defer span.End()
blockCid := resolvedPath.Cid()
blockReader, err := i.api.Block().Get(ctx, resolvedPath)
if err != nil {
webError(w, "ipfs block get "+blockCid.String(), err, http.StatusInternalServerError)
return
}
block, err := io.ReadAll(blockReader)
if err != nil {
webError(w, "ipfs block get "+blockCid.String(), err, http.StatusInternalServerError)
return
}
content := bytes.NewReader(block)
// Set Content-Disposition
var name string
if urlFilename := r.URL.Query().Get("filename"); urlFilename != "" {
name = urlFilename
} else {
name = blockCid.String() + ".bin"
}
setContentDispositionHeader(w, name, "attachment")
// Set remaining headers
modtime := addCacheControlHeaders(w, r, contentPath, blockCid)
w.Header().Set("Content-Type", "application/vnd.ipld.raw")
w.Header().Set("X-Content-Type-Options", "nosniff") // no funny business in the browsers :^)
// ServeContent will take care of
// If-None-Match+Etag, Content-Length and range requests
_, dataSent, _ := ServeContent(w, r, name, modtime, content)
if dataSent {
// Update metrics
i.rawBlockGetMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds())
}
}

View File

@ -1,99 +0,0 @@
package corehttp
import (
"context"
"fmt"
"net/http"
"time"
blocks "github.com/ipfs/go-block-format"
cid "github.com/ipfs/go-cid"
coreiface "github.com/ipfs/interface-go-ipfs-core"
ipath "github.com/ipfs/interface-go-ipfs-core/path"
"github.com/ipfs/kubo/tracing"
gocar "github.com/ipld/go-car"
selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// serveCAR returns a CAR stream for specific DAG+selector
func (i *gatewayHandler) serveCAR(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, carVersion string, begin time.Time) {
ctx, span := tracing.Span(ctx, "Gateway", "ServeCAR", trace.WithAttributes(attribute.String("path", resolvedPath.String())))
defer span.End()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
switch carVersion {
case "": // noop, client does not care about version
case "1": // noop, we support this
default:
err := fmt.Errorf("only version=1 is supported")
webError(w, "unsupported CAR version", err, http.StatusBadRequest)
return
}
rootCid := resolvedPath.Cid()
// Set Content-Disposition
var name string
if urlFilename := r.URL.Query().Get("filename"); urlFilename != "" {
name = urlFilename
} else {
name = rootCid.String() + ".car"
}
setContentDispositionHeader(w, name, "attachment")
// Set Cache-Control (same logic as for a regular files)
addCacheControlHeaders(w, r, contentPath, rootCid)
// Weak Etag W/ because we can't guarantee byte-for-byte identical
// responses, but still want to benefit from HTTP Caching. Two CAR
// responses for the same CID and selector will be logically equivalent,
// but when CAR is streamed, then in theory, blocks may arrive from
// datastore in non-deterministic order.
etag := `W/` + getEtag(r, rootCid)
w.Header().Set("Etag", etag)
// Finish early if Etag match
if r.Header.Get("If-None-Match") == etag {
w.WriteHeader(http.StatusNotModified)
return
}
// Make it clear we don't support range-requests over a car stream
// Partial downloads and resumes should be handled using requests for
// sub-DAGs and IPLD selectors: https://github.com/ipfs/go-ipfs/issues/8769
w.Header().Set("Accept-Ranges", "none")
w.Header().Set("Content-Type", "application/vnd.ipld.car; version=1")
w.Header().Set("X-Content-Type-Options", "nosniff") // no funny business in the browsers :^)
// Same go-car settings as dag.export command
store := dagStore{dag: i.api.Dag(), ctx: ctx}
// TODO: support selectors passed as request param: https://github.com/ipfs/kubo/issues/8769
dag := gocar.Dag{Root: rootCid, Selector: selectorparse.CommonSelector_ExploreAllRecursively}
car := gocar.NewSelectiveCar(ctx, store, []gocar.Dag{dag}, gocar.TraverseLinksOnlyOnce())
if err := car.Write(w); err != nil {
// We return error as a trailer, however it is not something browsers can access
// (https://github.com/mdn/browser-compat-data/issues/14703)
// Due to this, we suggest client always verify that
// the received CAR stream response is matching requested DAG selector
w.Header().Set("X-Stream-Error", err.Error())
return
}
// Update metrics
i.carStreamGetMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds())
}
// FIXME(@Jorropo): https://github.com/ipld/go-car/issues/315
type dagStore struct {
dag coreiface.APIDagService
ctx context.Context
}
func (ds dagStore) Get(_ context.Context, c cid.Cid) (blocks.Block, error) {
return ds.dag.Get(ds.ctx, c)
}

View File

@ -1,258 +0,0 @@
package corehttp
import (
"bytes"
"context"
"fmt"
"html"
"io"
"net/http"
"strings"
"time"
cid "github.com/ipfs/go-cid"
ipldlegacy "github.com/ipfs/go-ipld-legacy"
ipath "github.com/ipfs/interface-go-ipfs-core/path"
"github.com/ipfs/kubo/assets"
dih "github.com/ipfs/kubo/assets/dag-index-html"
"github.com/ipfs/kubo/tracing"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/multicodec"
mc "github.com/multiformats/go-multicodec"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// codecToContentType maps the supported IPLD codecs to the HTTP Content
// Type they should have.
var codecToContentType = map[uint64]string{
uint64(mc.Json): "application/json",
uint64(mc.Cbor): "application/cbor",
uint64(mc.DagJson): "application/vnd.ipld.dag-json",
uint64(mc.DagCbor): "application/vnd.ipld.dag-cbor",
}
// contentTypeToCodecs maps the HTTP Content Type to the respective
// possible codecs. If the original data is in one of those codecs,
// we stream the raw bytes. Otherwise, we encode in the last codec
// of the list.
var contentTypeToCodecs = map[string][]uint64{
"application/json": {uint64(mc.Json), uint64(mc.DagJson)},
"application/vnd.ipld.dag-json": {uint64(mc.DagJson)},
"application/cbor": {uint64(mc.Cbor), uint64(mc.DagCbor)},
"application/vnd.ipld.dag-cbor": {uint64(mc.DagCbor)},
}
// contentTypeToExtension maps the HTTP Content Type to the respective file
// extension, used in Content-Disposition header when downloading the file.
var contentTypeToExtension = map[string]string{
"application/json": ".json",
"application/vnd.ipld.dag-json": ".json",
"application/cbor": ".cbor",
"application/vnd.ipld.dag-cbor": ".cbor",
}
func (i *gatewayHandler) serveCodec(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, begin time.Time, requestedContentType string) {
ctx, span := tracing.Span(ctx, "Gateway", "ServeCodec", trace.WithAttributes(attribute.String("path", resolvedPath.String()), attribute.String("requestedContentType", requestedContentType)))
defer span.End()
cidCodec := resolvedPath.Cid().Prefix().Codec
responseContentType := requestedContentType
// If the resolved path still has some remainder, return error for now.
// TODO: handle this when we have IPLD Patch (https://ipld.io/specs/patch/) via HTTP PUT
// TODO: (depends on https://github.com/ipfs/kubo/issues/4801 and https://github.com/ipfs/kubo/issues/4782)
if resolvedPath.Remainder() != "" {
path := strings.TrimSuffix(resolvedPath.String(), resolvedPath.Remainder())
err := fmt.Errorf("%q of %q could not be returned: reading IPLD Kinds other than Links (CBOR Tag 42) is not implemented: try reading %q instead", resolvedPath.Remainder(), resolvedPath.String(), path)
webError(w, "unsupported pathing", err, http.StatusNotImplemented)
return
}
// If no explicit content type was requested, the response will have one based on the codec from the CID
if requestedContentType == "" {
cidContentType, ok := codecToContentType[cidCodec]
if !ok {
// Should not happen unless function is called with wrong parameters.
err := fmt.Errorf("content type not found for codec: %v", cidCodec)
webError(w, "internal error", err, http.StatusInternalServerError)
return
}
responseContentType = cidContentType
}
// Set HTTP headers (for caching etc)
modtime := addCacheControlHeaders(w, r, contentPath, resolvedPath.Cid())
name := setCodecContentDisposition(w, r, resolvedPath, responseContentType)
w.Header().Set("Content-Type", responseContentType)
w.Header().Set("X-Content-Type-Options", "nosniff")
// No content type is specified by the user (via Accept, or format=). However,
// we support this format. Let's handle it.
if requestedContentType == "" {
isDAG := cidCodec == uint64(mc.DagJson) || cidCodec == uint64(mc.DagCbor)
acceptsHTML := strings.Contains(r.Header.Get("Accept"), "text/html")
download := r.URL.Query().Get("download") == "true"
if isDAG && acceptsHTML && !download {
i.serveCodecHTML(ctx, w, r, resolvedPath, contentPath)
} else {
i.serveCodecRaw(ctx, w, r, resolvedPath, contentPath, name, modtime)
}
return
}
// Otherwise, the user has requested a specific content type. Let's first get
// the codecs that can be used with this content type.
codecs, ok := contentTypeToCodecs[requestedContentType]
if !ok {
// This is never supposed to happen unless function is called with wrong parameters.
err := fmt.Errorf("unsupported content type: %s", requestedContentType)
webError(w, err.Error(), err, http.StatusInternalServerError)
return
}
// If we need to convert, use the last codec (strict dag- variant)
toCodec := codecs[len(codecs)-1]
// If the requested content type has "dag-", ALWAYS go through the encoding
// process in order to validate the content.
if strings.Contains(requestedContentType, "dag-") {
i.serveCodecConverted(ctx, w, r, resolvedPath, contentPath, toCodec, modtime)
return
}
// Otherwise, check if the data is encoded with the requested content type.
// If so, we can directly stream the raw data. serveRawBlock cannot be directly
// used here as it sets different headers.
for _, codec := range codecs {
if resolvedPath.Cid().Prefix().Codec == codec {
i.serveCodecRaw(ctx, w, r, resolvedPath, contentPath, name, modtime)
return
}
}
// Finally, if nothing of the above is true, we have to actually convert the codec.
i.serveCodecConverted(ctx, w, r, resolvedPath, contentPath, toCodec, modtime)
}
func (i *gatewayHandler) serveCodecHTML(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path) {
// A HTML directory index will be presented, be sure to set the correct
// type instead of relying on autodetection (which may fail).
w.Header().Set("Content-Type", "text/html")
// Clear Content-Disposition -- we want HTML to be rendered inline
w.Header().Del("Content-Disposition")
// Generated index requires custom Etag (output may change between Kubo versions)
dagEtag := getDagIndexEtag(resolvedPath.Cid())
w.Header().Set("Etag", dagEtag)
// Remove Cache-Control for now to match UnixFS dir-index-html responses
// (we don't want browser to cache HTML forever)
// TODO: if we ever change behavior for UnixFS dir listings, same changes should be applied here
w.Header().Del("Cache-Control")
cidCodec := mc.Code(resolvedPath.Cid().Prefix().Codec)
if err := dih.DagIndexTemplate.Execute(w, dih.DagIndexTemplateData{
Path: contentPath.String(),
CID: resolvedPath.Cid().String(),
CodecName: cidCodec.String(),
CodecHex: fmt.Sprintf("0x%x", uint64(cidCodec)),
}); err != nil {
webError(w, "failed to generate HTML listing for this DAG: try fetching raw block with ?format=raw", err, http.StatusInternalServerError)
}
}
func (i *gatewayHandler) serveCodecRaw(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, name string, modtime time.Time) {
blockCid := resolvedPath.Cid()
blockReader, err := i.api.Block().Get(ctx, resolvedPath)
if err != nil {
webError(w, "ipfs block get "+blockCid.String(), err, http.StatusInternalServerError)
return
}
block, err := io.ReadAll(blockReader)
if err != nil {
webError(w, "ipfs block get "+blockCid.String(), err, http.StatusInternalServerError)
return
}
content := bytes.NewReader(block)
// ServeContent will take care of
// If-None-Match+Etag, Content-Length and range requests
_, _, _ = ServeContent(w, r, name, modtime, content)
}
func (i *gatewayHandler) serveCodecConverted(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, toCodec uint64, modtime time.Time) {
obj, err := i.api.Dag().Get(ctx, resolvedPath.Cid())
if err != nil {
webError(w, "ipfs dag get "+html.EscapeString(resolvedPath.String()), err, http.StatusInternalServerError)
return
}
universal, ok := obj.(ipldlegacy.UniversalNode)
if !ok {
err = fmt.Errorf("%T is not a valid IPLD node", obj)
webError(w, err.Error(), err, http.StatusInternalServerError)
return
}
finalNode := universal.(ipld.Node)
encoder, err := multicodec.LookupEncoder(toCodec)
if err != nil {
webError(w, err.Error(), err, http.StatusInternalServerError)
return
}
// Ensure IPLD node conforms to the codec specification.
var buf bytes.Buffer
err = encoder(finalNode, &buf)
if err != nil {
webError(w, err.Error(), err, http.StatusInternalServerError)
return
}
// Sets correct Last-Modified header. This code is borrowed from the standard
// library (net/http/server.go) as we cannot use serveFile.
if !(modtime.IsZero() || modtime.Equal(unixEpochTime)) {
w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat))
}
_, _ = w.Write(buf.Bytes())
}
func setCodecContentDisposition(w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentType string) string {
var dispType, name string
ext, ok := contentTypeToExtension[contentType]
if !ok {
// Should never happen.
ext = ".bin"
}
if urlFilename := r.URL.Query().Get("filename"); urlFilename != "" {
name = urlFilename
} else {
name = resolvedPath.Cid().String() + ext
}
// JSON should be inlined, but ?download=true should still override
if r.URL.Query().Get("download") == "true" {
dispType = "attachment"
} else {
switch ext {
case ".json": // codecs that serialize to JSON can be rendered by browsers
dispType = "inline"
default: // everything else is assumed binary / opaque bytes
dispType = "attachment"
}
}
setContentDispositionHeader(w, name, dispType)
return name
}
func getDagIndexEtag(dagCid cid.Cid) string {
return `"DagIndex-` + assets.AssetHash + `_CID-` + dagCid.String() + `"`
}

View File

@ -1,92 +0,0 @@
package corehttp
import (
"context"
"html"
"net/http"
"time"
files "github.com/ipfs/go-ipfs-files"
ipath "github.com/ipfs/interface-go-ipfs-core/path"
"github.com/ipfs/kubo/tracing"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
)
var unixEpochTime = time.Unix(0, 0)
func (i *gatewayHandler) serveTAR(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, begin time.Time, logger *zap.SugaredLogger) {
ctx, span := tracing.Span(ctx, "Gateway", "ServeTAR", trace.WithAttributes(attribute.String("path", resolvedPath.String())))
defer span.End()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// Get Unixfs file
file, err := i.api.Unixfs().Get(ctx, resolvedPath)
if err != nil {
webError(w, "ipfs cat "+html.EscapeString(contentPath.String()), err, http.StatusBadRequest)
return
}
defer file.Close()
rootCid := resolvedPath.Cid()
// Set Cache-Control and read optional Last-Modified time
modtime := addCacheControlHeaders(w, r, contentPath, rootCid)
// Weak Etag W/ because we can't guarantee byte-for-byte identical
// responses, but still want to benefit from HTTP Caching. Two TAR
// responses for the same CID will be logically equivalent,
// but when TAR is streamed, then in theory, files and directories
// may arrive in different order (depends on TAR lib and filesystem/inodes).
etag := `W/` + getEtag(r, rootCid)
w.Header().Set("Etag", etag)
// Finish early if Etag match
if r.Header.Get("If-None-Match") == etag {
w.WriteHeader(http.StatusNotModified)
return
}
// Set Content-Disposition
var name string
if urlFilename := r.URL.Query().Get("filename"); urlFilename != "" {
name = urlFilename
} else {
name = rootCid.String() + ".tar"
}
setContentDispositionHeader(w, name, "attachment")
// Construct the TAR writer
tarw, err := files.NewTarWriter(w)
if err != nil {
webError(w, "could not build tar writer", err, http.StatusInternalServerError)
return
}
defer tarw.Close()
// Sets correct Last-Modified header. This code is borrowed from the standard
// library (net/http/server.go) as we cannot use serveFile without throwing the entire
// TAR into the memory first.
if !(modtime.IsZero() || modtime.Equal(unixEpochTime)) {
w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat))
}
w.Header().Set("Content-Type", "application/x-tar")
w.Header().Set("X-Content-Type-Options", "nosniff") // no funny business in the browsers :^)
// The TAR has a top-level directory (or file) named by the CID.
if err := tarw.WriteFile(file, rootCid.String()); err != nil {
w.Header().Set("X-Stream-Error", err.Error())
// Trailer headers do not work in web browsers
// (see https://github.com/mdn/browser-compat-data/issues/14703)
// and we have limited options around error handling in browser contexts.
// To improve UX/DX, we finish response stream with error message, allowing client to
// (1) detect error by having corrupted TAR
// (2) be able to reason what went wrong by instecting the tail of TAR stream
_, _ = w.Write([]byte(err.Error()))
return
}
}

View File

@ -1,46 +0,0 @@
package corehttp
import (
"context"
"fmt"
"html"
"net/http"
"time"
files "github.com/ipfs/go-ipfs-files"
ipath "github.com/ipfs/interface-go-ipfs-core/path"
"github.com/ipfs/kubo/tracing"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
)
func (i *gatewayHandler) serveUnixFS(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, begin time.Time, logger *zap.SugaredLogger) {
ctx, span := tracing.Span(ctx, "Gateway", "ServeUnixFS", trace.WithAttributes(attribute.String("path", resolvedPath.String())))
defer span.End()
// Handling UnixFS
dr, err := i.api.Unixfs().Get(ctx, resolvedPath)
if err != nil {
webError(w, "ipfs cat "+html.EscapeString(contentPath.String()), err, http.StatusBadRequest)
return
}
defer dr.Close()
// Handling Unixfs file
if f, ok := dr.(files.File); ok {
logger.Debugw("serving unixfs file", "path", contentPath)
i.serveFile(ctx, w, r, resolvedPath, contentPath, f, begin)
return
}
// Handling Unixfs directory
dir, ok := dr.(files.Directory)
if !ok {
internalWebError(w, fmt.Errorf("unsupported UnixFS type"))
return
}
logger.Debugw("serving unixfs directory", "path", contentPath)
i.serveDirectory(ctx, w, r, resolvedPath, contentPath, dir, begin, logger)
}

View File

@ -1,287 +0,0 @@
package corehttp
import (
"fmt"
"io"
"net/http"
gopath "path"
"strconv"
"strings"
files "github.com/ipfs/go-ipfs-files"
redirects "github.com/ipfs/go-ipfs-redirects-file"
ipath "github.com/ipfs/interface-go-ipfs-core/path"
"go.uber.org/zap"
)
// Resolving a UnixFS path involves determining if the provided `path.Path` exists and returning the `path.Resolved`
// corresponding to that path. For UnixFS, path resolution is more involved.
//
// When a path under requested CID does not exist, Gateway will check if a `_redirects` file exists
// underneath the root CID of the path, and apply rules defined there.
// See sepcification introduced in: https://github.com/ipfs/specs/pull/290
//
// Scenario 1:
// If a path exists, we always return the `path.Resolved` corresponding to that path, regardless of the existence of a `_redirects` file.
//
// Scenario 2:
// If a path does not exist, usually we should return a `nil` resolution path and an error indicating that the path
// doesn't exist. However, a `_redirects` file may exist and contain a redirect rule that redirects that path to a different path.
// We need to evaluate the rule and perform the redirect if present.
//
// Scenario 3:
// Another possibility is that the path corresponds to a rewrite rule (i.e. a rule with a status of 200).
// In this case, we don't perform a redirect, but do need to return a `path.Resolved` and `path.Path` corresponding to
// the rewrite destination path.
//
// Note that for security reasons, redirect rules are only processed when the request has origin isolation.
// See https://github.com/ipfs/specs/pull/290 for more information.
func (i *gatewayHandler) serveRedirectsIfPresent(w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, logger *zap.SugaredLogger) (newResolvedPath ipath.Resolved, newContentPath ipath.Path, continueProcessing bool, hadMatchingRule bool) {
redirectsFile := i.getRedirectsFile(r, contentPath, logger)
if redirectsFile != nil {
redirectRules, err := i.getRedirectRules(r, redirectsFile)
if err != nil {
internalWebError(w, err)
return nil, nil, false, true
}
redirected, newPath, err := i.handleRedirectsFileRules(w, r, contentPath, redirectRules)
if err != nil {
err = fmt.Errorf("trouble processing _redirects file at %q: %w", redirectsFile.String(), err)
internalWebError(w, err)
return nil, nil, false, true
}
if redirected {
return nil, nil, false, true
}
// 200 is treated as a rewrite, so update the path and continue
if newPath != "" {
// Reassign contentPath and resolvedPath since the URL was rewritten
contentPath = ipath.New(newPath)
resolvedPath, err = i.api.ResolvePath(r.Context(), contentPath)
if err != nil {
internalWebError(w, err)
return nil, nil, false, true
}
return resolvedPath, contentPath, true, true
}
}
// No matching rule, paths remain the same, continue regular processing
return resolvedPath, contentPath, true, false
}
func (i *gatewayHandler) handleRedirectsFileRules(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, redirectRules []redirects.Rule) (redirected bool, newContentPath string, err error) {
// Attempt to match a rule to the URL path, and perform the corresponding redirect or rewrite
pathParts := strings.Split(contentPath.String(), "/")
if len(pathParts) > 3 {
// All paths should start with /ipfs/cid/, so get the path after that
urlPath := "/" + strings.Join(pathParts[3:], "/")
rootPath := strings.Join(pathParts[:3], "/")
// Trim off the trailing /
urlPath = strings.TrimSuffix(urlPath, "/")
for _, rule := range redirectRules {
// Error right away if the rule is invalid
if !rule.MatchAndExpandPlaceholders(urlPath) {
continue
}
// We have a match!
// Rewrite
if rule.Status == 200 {
// Prepend the rootPath
toPath := rootPath + rule.To
return false, toPath, nil
}
// Or 4xx
if rule.Status == 404 || rule.Status == 410 || rule.Status == 451 {
toPath := rootPath + rule.To
content4xxPath := ipath.New(toPath)
err := i.serve4xx(w, r, content4xxPath, rule.Status)
return true, toPath, err
}
// Or redirect
if rule.Status >= 301 && rule.Status <= 308 {
http.Redirect(w, r, rule.To, rule.Status)
return true, "", nil
}
}
}
// No redirects matched
return false, "", nil
}
func (i *gatewayHandler) getRedirectRules(r *http.Request, redirectsFilePath ipath.Resolved) ([]redirects.Rule, error) {
// Convert the path into a file node
node, err := i.api.Unixfs().Get(r.Context(), redirectsFilePath)
if err != nil {
return nil, fmt.Errorf("could not get _redirects: %w", err)
}
defer node.Close()
// Convert the node into a file
f, ok := node.(files.File)
if !ok {
return nil, fmt.Errorf("could not parse _redirects: %w", err)
}
// Parse redirect rules from file
redirectRules, err := redirects.Parse(f)
if err != nil {
return nil, fmt.Errorf("could not parse _redirects: %w", err)
}
return redirectRules, nil
}
// Returns a resolved path to the _redirects file located in the root CID path of the requested path
func (i *gatewayHandler) getRedirectsFile(r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) ipath.Resolved {
// contentPath is the full ipfs path to the requested resource,
// regardless of whether path or subdomain resolution is used.
rootPath := getRootPath(contentPath)
// Check for _redirects file.
// Any path resolution failures are ignored and we just assume there's no _redirects file.
// Note that ignoring these errors also ensures that the use of the empty CID (bafkqaaa) in tests doesn't fail.
path := ipath.Join(rootPath, "_redirects")
resolvedPath, err := i.api.ResolvePath(r.Context(), path)
if err != nil {
return nil
}
return resolvedPath
}
// Returns the root CID Path for the given path
func getRootPath(path ipath.Path) ipath.Path {
parts := strings.Split(path.String(), "/")
return ipath.New(gopath.Join("/", path.Namespace(), parts[2]))
}
func (i *gatewayHandler) serve4xx(w http.ResponseWriter, r *http.Request, content4xxPath ipath.Path, status int) error {
resolved4xxPath, err := i.api.ResolvePath(r.Context(), content4xxPath)
if err != nil {
return err
}
node, err := i.api.Unixfs().Get(r.Context(), resolved4xxPath)
if err != nil {
return err
}
defer node.Close()
f, ok := node.(files.File)
if !ok {
return fmt.Errorf("could not convert node for %d page to file", status)
}
size, err := f.Size()
if err != nil {
return fmt.Errorf("could not get size of %d page", status)
}
log.Debugf("using _redirects: custom %d file at %q", status, content4xxPath)
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
addCacheControlHeaders(w, r, content4xxPath, resolved4xxPath.Cid())
w.WriteHeader(status)
_, err = io.CopyN(w, f, size)
return err
}
func hasOriginIsolation(r *http.Request) bool {
_, gw := r.Context().Value(requestContextKey("gw-hostname")).(string)
_, dnslink := r.Context().Value("dnslink-hostname").(string)
if gw || dnslink {
return true
}
return false
}
func isUnixfsResponseFormat(responseFormat string) bool {
// The implicit response format is UnixFS
return responseFormat == ""
}
// Deprecated: legacy ipfs-404.html files are superseded by _redirects file
// This is provided only for backward-compatibility, until websites migrate
// to 404s managed via _redirects file (https://github.com/ipfs/specs/pull/290)
func (i *gatewayHandler) serveLegacy404IfPresent(w http.ResponseWriter, r *http.Request, contentPath ipath.Path) bool {
resolved404Path, ctype, err := i.searchUpTreeFor404(r, contentPath)
if err != nil {
return false
}
dr, err := i.api.Unixfs().Get(r.Context(), resolved404Path)
if err != nil {
return false
}
defer dr.Close()
f, ok := dr.(files.File)
if !ok {
return false
}
size, err := f.Size()
if err != nil {
return false
}
log.Debugw("using pretty 404 file", "path", contentPath)
w.Header().Set("Content-Type", ctype)
w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
w.WriteHeader(http.StatusNotFound)
_, err = io.CopyN(w, f, size)
return err == nil
}
func (i *gatewayHandler) searchUpTreeFor404(r *http.Request, contentPath ipath.Path) (ipath.Resolved, string, error) {
filename404, ctype, err := preferred404Filename(r.Header.Values("Accept"))
if err != nil {
return nil, "", err
}
pathComponents := strings.Split(contentPath.String(), "/")
for idx := len(pathComponents); idx >= 3; idx-- {
pretty404 := gopath.Join(append(pathComponents[0:idx], filename404)...)
parsed404Path := ipath.New("/" + pretty404)
if parsed404Path.IsValid() != nil {
break
}
resolvedPath, err := i.api.ResolvePath(r.Context(), parsed404Path)
if err != nil {
continue
}
return resolvedPath, ctype, nil
}
return nil, "", fmt.Errorf("no pretty 404 in any parent folder")
}
func preferred404Filename(acceptHeaders []string) (string, string, error) {
// If we ever want to offer a 404 file for a different content type
// then this function will need to parse q weightings, but for now
// the presence of anything matching HTML is enough.
for _, acceptHeader := range acceptHeaders {
accepted := strings.Split(acceptHeader, ",")
for _, spec := range accepted {
contentType := strings.SplitN(spec, ";", 1)[0]
switch contentType {
case "*/*", "text/*", "text/html":
return "ipfs-404.html", "text/html", nil
}
}
}
return "", "", fmt.Errorf("there is no 404 file for the requested content types")
}

View File

@ -1,222 +0,0 @@
package corehttp
import (
"context"
"net/http"
"net/url"
gopath "path"
"strings"
"time"
"github.com/dustin/go-humanize"
cid "github.com/ipfs/go-cid"
files "github.com/ipfs/go-ipfs-files"
path "github.com/ipfs/go-path"
"github.com/ipfs/go-path/resolver"
options "github.com/ipfs/interface-go-ipfs-core/options"
ipath "github.com/ipfs/interface-go-ipfs-core/path"
"github.com/ipfs/kubo/assets"
"github.com/ipfs/kubo/tracing"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
)
// serveDirectory returns the best representation of UnixFS directory
//
// It will return index.html if present, or generate directory listing otherwise.
func (i *gatewayHandler) serveDirectory(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, dir files.Directory, begin time.Time, logger *zap.SugaredLogger) {
ctx, span := tracing.Span(ctx, "Gateway", "ServeDirectory", trace.WithAttributes(attribute.String("path", resolvedPath.String())))
defer span.End()
// HostnameOption might have constructed an IPNS/IPFS path using the Host header.
// In this case, we need the original path for constructing redirects
// and links that match the requested URL.
// For example, http://example.net would become /ipns/example.net, and
// the redirects and links would end up as http://example.net/ipns/example.net
requestURI, err := url.ParseRequestURI(r.RequestURI)
if err != nil {
webError(w, "failed to parse request path", err, http.StatusInternalServerError)
return
}
originalURLPath := requestURI.Path
// Ensure directory paths end with '/'
if originalURLPath[len(originalURLPath)-1] != '/' {
// don't redirect to trailing slash if it's go get
// https://github.com/ipfs/kubo/pull/3963
goget := r.URL.Query().Get("go-get") == "1"
if !goget {
suffix := "/"
// preserve query parameters
if r.URL.RawQuery != "" {
suffix = suffix + "?" + r.URL.RawQuery
}
// /ipfs/cid/foo?bar must be redirected to /ipfs/cid/foo/?bar
redirectURL := originalURLPath + suffix
logger.Debugw("directory location moved permanently", "status", http.StatusMovedPermanently)
http.Redirect(w, r, redirectURL, http.StatusMovedPermanently)
return
}
}
// Check if directory has index.html, if so, serveFile
idxPath := ipath.Join(contentPath, "index.html")
idx, err := i.api.Unixfs().Get(ctx, idxPath)
switch err.(type) {
case nil:
f, ok := idx.(files.File)
if !ok {
internalWebError(w, files.ErrNotReader)
return
}
logger.Debugw("serving index.html file", "path", idxPath)
// write to request
i.serveFile(ctx, w, r, resolvedPath, idxPath, f, begin)
return
case resolver.ErrNoLink:
logger.Debugw("no index.html; noop", "path", idxPath)
default:
internalWebError(w, err)
return
}
// See statusResponseWriter.WriteHeader
// and https://github.com/ipfs/kubo/issues/7164
// Note: this needs to occur before listingTemplate.Execute otherwise we get
// superfluous response.WriteHeader call from prometheus/client_golang
if w.Header().Get("Location") != "" {
logger.Debugw("location moved permanently", "status", http.StatusMovedPermanently)
w.WriteHeader(http.StatusMovedPermanently)
return
}
// A HTML directory index will be presented, be sure to set the correct
// type instead of relying on autodetection (which may fail).
w.Header().Set("Content-Type", "text/html")
// Generated dir index requires custom Etag (output may change between go-ipfs versions)
dirEtag := getDirListingEtag(resolvedPath.Cid())
w.Header().Set("Etag", dirEtag)
if r.Method == http.MethodHead {
logger.Debug("return as request's HTTP method is HEAD")
return
}
// Optimization 1:
// List children without fetching their root blocks (fast, but no size info)
results, err := i.api.Unixfs().Ls(ctx, resolvedPath, options.Unixfs.ResolveChildren(false))
if err != nil {
internalWebError(w, err)
return
}
// storage for directory listing
dirListing := make([]directoryItem, 0, len(results))
for link := range results {
if link.Err != nil {
internalWebError(w, err)
return
}
hash := link.Cid.String()
di := directoryItem{
Size: "", // no size because we did not fetch child nodes
Name: link.Name,
Path: gopath.Join(originalURLPath, link.Name),
Hash: hash,
ShortHash: shortHash(hash),
}
dirListing = append(dirListing, di)
}
// Optimization 2: fetch sizes only for dirs below FastDirIndexThreshold
if len(dirListing) < i.config.FastDirIndexThreshold {
dirit := dir.Entries()
linkNo := 0
for dirit.Next() {
size := "?"
if s, err := dirit.Node().Size(); err == nil {
// Size may not be defined/supported. Continue anyways.
size = humanize.Bytes(uint64(s))
}
dirListing[linkNo].Size = size
linkNo++
}
}
// construct the correct back link
// https://github.com/ipfs/kubo/issues/1365
backLink := originalURLPath
// don't go further up than /ipfs/$hash/
pathSplit := path.SplitList(contentPath.String())
switch {
// skip backlink when listing a content root
case len(pathSplit) == 3: // url: /ipfs/$hash
backLink = ""
// skip backlink when listing a content root
case len(pathSplit) == 4 && pathSplit[3] == "": // url: /ipfs/$hash/
backLink = ""
// add the correct link depending on whether the path ends with a slash
default:
if strings.HasSuffix(backLink, "/") {
backLink += ".."
} else {
backLink += "/.."
}
}
size := "?"
if s, err := dir.Size(); err == nil {
// Size may not be defined/supported. Continue anyways.
size = humanize.Bytes(uint64(s))
}
hash := resolvedPath.Cid().String()
// Gateway root URL to be used when linking to other rootIDs.
// This will be blank unless subdomain or DNSLink resolution is being used
// for this request.
var gwURL string
// Get gateway hostname and build gateway URL.
if h, ok := r.Context().Value(requestContextKey("gw-hostname")).(string); ok {
gwURL = "//" + h
} else {
gwURL = ""
}
dnslink := hasDNSLinkOrigin(gwURL, contentPath.String())
// See comment above where originalUrlPath is declared.
tplData := listingTemplateData{
GatewayURL: gwURL,
DNSLink: dnslink,
Listing: dirListing,
Size: size,
Path: contentPath.String(),
Breadcrumbs: breadcrumbs(contentPath.String(), dnslink),
BackLink: backLink,
Hash: hash,
FastDirIndexThreshold: i.config.FastDirIndexThreshold,
}
logger.Debugw("request processed", "tplDataDNSLink", dnslink, "tplDataSize", size, "tplDataBackLink", backLink, "tplDataHash", hash)
if err := listingTemplate.Execute(w, tplData); err != nil {
internalWebError(w, err)
return
}
// Update metrics
i.unixfsGenDirGetMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds())
}
func getDirListingEtag(dirCid cid.Cid) string {
return `"DirIndex-` + assets.AssetHash + `_CID-` + dirCid.String() + `"`
}

View File

@ -1,104 +0,0 @@
package corehttp
import (
"context"
"fmt"
"io"
"mime"
"net/http"
gopath "path"
"strings"
"time"
"github.com/gabriel-vasile/mimetype"
files "github.com/ipfs/go-ipfs-files"
ipath "github.com/ipfs/interface-go-ipfs-core/path"
"github.com/ipfs/kubo/tracing"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// serveFile returns data behind a file along with HTTP headers based on
// the file itself, its CID and the contentPath used for accessing it.
func (i *gatewayHandler) serveFile(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, file files.File, begin time.Time) {
_, span := tracing.Span(ctx, "Gateway", "ServeFile", trace.WithAttributes(attribute.String("path", resolvedPath.String())))
defer span.End()
// Set Cache-Control and read optional Last-Modified time
modtime := addCacheControlHeaders(w, r, contentPath, resolvedPath.Cid())
// Set Content-Disposition
name := addContentDispositionHeader(w, r, contentPath)
// Prepare size value for Content-Length HTTP header (set inside of http.ServeContent)
size, err := file.Size()
if err != nil {
http.Error(w, "cannot serve files with unknown sizes", http.StatusBadGateway)
return
}
if size == 0 {
// We override null files to 200 to avoid issues with fragment caching reverse proxies.
// Also whatever you are asking for, it's cheaper to just give you the complete file (nothing).
// TODO: remove this if clause once https://github.com/golang/go/issues/54794 is fixed in two latest releases of go
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
return
}
// Lazy seeker enables efficient range-requests and HTTP HEAD responses
content := &lazySeeker{
size: size,
reader: file,
}
// Calculate deterministic value for Content-Type HTTP header
// (we prefer to do it here, rather than using implicit sniffing in http.ServeContent)
var ctype string
if _, isSymlink := file.(*files.Symlink); isSymlink {
// We should be smarter about resolving symlinks but this is the
// "most correct" we can be without doing that.
ctype = "inode/symlink"
} else {
ctype = mime.TypeByExtension(gopath.Ext(name))
if ctype == "" {
// uses https://github.com/gabriel-vasile/mimetype library to determine the content type.
// Fixes https://github.com/ipfs/kubo/issues/7252
mimeType, err := mimetype.DetectReader(content)
if err != nil {
http.Error(w, fmt.Sprintf("cannot detect content-type: %s", err.Error()), http.StatusInternalServerError)
return
}
ctype = mimeType.String()
_, err = content.Seek(0, io.SeekStart)
if err != nil {
http.Error(w, "seeker can't seek", http.StatusInternalServerError)
return
}
}
// Strip the encoding from the HTML Content-Type header and let the
// browser figure it out.
//
// Fixes https://github.com/ipfs/kubo/issues/2203
if strings.HasPrefix(ctype, "text/html;") {
ctype = "text/html"
}
}
// Setting explicit Content-Type to avoid mime-type sniffing on the client
// (unifies behavior across gateways and web browsers)
w.Header().Set("Content-Type", ctype)
// special fixup around redirects
w = &statusResponseWriter{w}
// ServeContent will take care of
// If-None-Match+Etag, Content-Length and range requests
_, dataSent, _ := ServeContent(w, r, name, modtime, content)
// Was response successful?
if dataSent {
// Update metrics
i.unixfsFileGetMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds())
}
}

View File

@ -1,134 +0,0 @@
package corehttp
import (
"html/template"
"net/url"
"path"
"strings"
ipfspath "github.com/ipfs/go-path"
"github.com/ipfs/kubo/assets"
)
// structs for directory listing
type listingTemplateData struct {
GatewayURL string
DNSLink bool
Listing []directoryItem
Size string
Path string
Breadcrumbs []breadcrumb
BackLink string
Hash string
FastDirIndexThreshold int
}
type directoryItem struct {
Size string
Name string
Path string
Hash string
ShortHash string
}
type breadcrumb struct {
Name string
Path string
}
func breadcrumbs(urlPath string, dnslinkOrigin bool) []breadcrumb {
var ret []breadcrumb
p, err := ipfspath.ParsePath(urlPath)
if err != nil {
// No breadcrumbs, fallback to bare Path in template
return ret
}
segs := p.Segments()
contentRoot := segs[1]
for i, seg := range segs {
if i == 0 {
ret = append(ret, breadcrumb{Name: seg})
} else {
ret = append(ret, breadcrumb{
Name: seg,
Path: "/" + strings.Join(segs[0:i+1], "/"),
})
}
}
// Drop the /ipns/<fqdn> prefix from breadcrumb Paths when directory
// listing on a DNSLink website (loaded due to Host header in HTTP
// request). Necessary because the hostname most likely won't have a
// public gateway mounted.
if dnslinkOrigin {
prefix := "/ipns/" + contentRoot
for i, crumb := range ret {
if strings.HasPrefix(crumb.Path, prefix) {
ret[i].Path = strings.Replace(crumb.Path, prefix, "", 1)
}
}
// Make contentRoot breadcrumb link to the website root
ret[1].Path = "/"
}
return ret
}
func shortHash(hash string) string {
if len(hash) <= 8 {
return hash
}
return (hash[0:4] + "\u2026" + hash[len(hash)-4:])
}
// helper to detect DNSLink website context
// (when hostname from gwURL is matching /ipns/<fqdn> in path)
func hasDNSLinkOrigin(gwURL string, path string) bool {
if gwURL != "" {
fqdn := stripPort(strings.TrimPrefix(gwURL, "//"))
return strings.HasPrefix(path, "/ipns/"+fqdn)
}
return false
}
var listingTemplate *template.Template
func init() {
knownIconsBytes, err := assets.Asset.ReadFile("dir-index-html/knownIcons.txt")
if err != nil {
panic(err)
}
knownIcons := make(map[string]struct{})
for _, ext := range strings.Split(strings.TrimSuffix(string(knownIconsBytes), "\n"), "\n") {
knownIcons[ext] = struct{}{}
}
// helper to guess the type/icon for it by the extension name
iconFromExt := func(name string) string {
ext := path.Ext(name)
_, ok := knownIcons[ext]
if !ok {
// default blank icon
return "ipfs-_blank"
}
return "ipfs-" + ext[1:] // slice of the first dot
}
// custom template-escaping function to escape a full path, including '#' and '?'
urlEscape := func(rawUrl string) string {
pathURL := url.URL{Path: rawUrl}
return pathURL.String()
}
// Directory listing template
dirIndexBytes, err := assets.Asset.ReadFile("dir-index-html/dir-index.html")
if err != nil {
panic(err)
}
listingTemplate = template.Must(template.New("dir").Funcs(template.FuncMap{
"iconFromExt": iconFromExt,
"urlEscape": urlEscape,
}).Parse(string(dirIndexBytes)))
}

View File

@ -9,7 +9,6 @@ import (
"regexp"
"strings"
"testing"
"time"
namesys "github.com/ipfs/go-namesys"
version "github.com/ipfs/kubo"
@ -19,7 +18,7 @@ import (
datastore "github.com/ipfs/go-datastore"
syncds "github.com/ipfs/go-datastore/sync"
files "github.com/ipfs/go-ipfs-files"
"github.com/ipfs/go-libipfs/files"
path "github.com/ipfs/go-path"
iface "github.com/ipfs/interface-go-ipfs-core"
nsopts "github.com/ipfs/interface-go-ipfs-core/options/namesys"
@ -68,11 +67,7 @@ func (m mockNamesys) ResolveAsync(ctx context.Context, name string, opts ...nsop
return out
}
func (m mockNamesys) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error {
return errors.New("not implemented for mockNamesys")
}
func (m mockNamesys) PublishWithEOL(ctx context.Context, name ci.PrivKey, value path.Path, _ time.Time) error {
func (m mockNamesys) Publish(ctx context.Context, name ci.PrivKey, value path.Path, opts ...nsopts.PublishOption) error {
return errors.New("not implemented for mockNamesys")
}
@ -656,28 +651,3 @@ func TestVersion(t *testing.T) {
t.Fatalf("response doesn't contain protocol version:\n%s", s)
}
}
func TestEtagMatch(t *testing.T) {
for _, test := range []struct {
header string // value in If-None-Match HTTP header
cidEtag string
dirEtag string
expected bool // expected result of etagMatch(header, cidEtag, dirEtag)
}{
{"", `"etag"`, "", false}, // no If-None-Match
{"", "", `"etag"`, false}, // no If-None-Match
{`"etag"`, `"etag"`, "", true}, // file etag match
{`W/"etag"`, `"etag"`, "", true}, // file etag match
{`"foo", W/"bar", W/"etag"`, `"etag"`, "", true}, // file etag match (array)
{`"foo",W/"bar",W/"etag"`, `"etag"`, "", true}, // file etag match (compact array)
{`"etag"`, "", `W/"etag"`, true}, // dir etag match
{`"etag"`, "", `W/"etag"`, true}, // dir etag match
{`W/"etag"`, "", `W/"etag"`, true}, // dir etag match
{`*`, `"etag"`, "", true}, // wildcard etag match
} {
result := etagMatch(test.header, test.cidEtag, test.dirEtag)
if result != test.expected {
t.Fatalf("unexpected result of etagMatch(%q, %q, %q), got %t, expected %t", test.header, test.cidEtag, test.dirEtag, result, test.expected)
}
}
}

View File

@ -10,6 +10,7 @@ import (
"strings"
cid "github.com/ipfs/go-cid"
"github.com/ipfs/go-libipfs/gateway"
namesys "github.com/ipfs/go-namesys"
core "github.com/ipfs/kubo/core"
coreapi "github.com/ipfs/kubo/core/coreapi"
@ -225,7 +226,7 @@ func HostnameOption() ServeOption {
if !cfg.Gateway.NoDNSLink && isDNSLinkName(r.Context(), coreAPI, host) {
// rewrite path and handle as DNSLink
r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path
ctx := context.WithValue(r.Context(), requestContextKey("dnslink-hostname"), host)
ctx := context.WithValue(r.Context(), gateway.DNSLinkHostnameKey, host)
childMux.ServeHTTP(w, withHostnameContext(r.WithContext(ctx), host))
return
}
@ -247,8 +248,6 @@ type wildcardHost struct {
spec *config.GatewaySpec
}
type requestContextKey string
// Extends request context to include hostname of a canonical gateway root
// (subdomain root or dnslink fqdn)
func withHostnameContext(r *http.Request, hostname string) *http.Request {
@ -257,7 +256,7 @@ func withHostnameContext(r *http.Request, hostname string) *http.Request {
// Host header, subdomain gateways have more comples rules (knownSubdomainDetails)
// More: https://github.com/ipfs/dir-index-html/issues/42
// nolint: staticcheck // non-backward compatible change
ctx := context.WithValue(r.Context(), requestContextKey("gw-hostname"), hostname)
ctx := context.WithValue(r.Context(), gateway.GatewayHostnameKey, hostname)
return r.WithContext(ctx)
}

View File

@ -7,7 +7,7 @@ import (
"testing"
cid "github.com/ipfs/go-cid"
files "github.com/ipfs/go-ipfs-files"
"github.com/ipfs/go-libipfs/files"
path "github.com/ipfs/go-path"
config "github.com/ipfs/kubo/config"
coreapi "github.com/ipfs/kubo/core/coreapi"

View File

@ -1,60 +0,0 @@
package corehttp
import (
"fmt"
"io"
)
// The HTTP server uses seek to determine the file size. Actually _seeking_ can
// be slow so we wrap the seeker in a _lazy_ seeker.
type lazySeeker struct {
reader io.ReadSeeker
size int64
offset int64
realOffset int64
}
func (s *lazySeeker) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekEnd:
return s.Seek(s.size+offset, io.SeekStart)
case io.SeekCurrent:
return s.Seek(s.offset+offset, io.SeekStart)
case io.SeekStart:
if offset < 0 {
return s.offset, fmt.Errorf("invalid seek offset")
}
s.offset = offset
return s.offset, nil
default:
return s.offset, fmt.Errorf("invalid whence: %d", whence)
}
}
func (s *lazySeeker) Read(b []byte) (int, error) {
// If we're past the end, EOF.
if s.offset >= s.size {
return 0, io.EOF
}
// actually seek
for s.offset != s.realOffset {
off, err := s.reader.Seek(s.offset, io.SeekStart)
if err != nil {
return 0, err
}
s.realOffset = off
}
off, err := s.reader.Read(b)
s.realOffset += int64(off)
s.offset += int64(off)
return off, err
}
func (s *lazySeeker) Close() error {
if closer, ok := s.reader.(io.Closer); ok {
return closer.Close()
}
return nil
}

View File

@ -1,136 +0,0 @@
package corehttp
import (
"fmt"
"io"
"strings"
"testing"
)
type badSeeker struct {
io.ReadSeeker
}
var errBadSeek = fmt.Errorf("bad seeker")
func (bs badSeeker) Seek(offset int64, whence int) (int64, error) {
off, err := bs.ReadSeeker.Seek(0, io.SeekCurrent)
if err != nil {
panic(err)
}
return off, errBadSeek
}
func TestLazySeekerError(t *testing.T) {
underlyingBuffer := strings.NewReader("fubar")
s := &lazySeeker{
reader: badSeeker{underlyingBuffer},
size: underlyingBuffer.Size(),
}
off, err := s.Seek(0, io.SeekEnd)
if err != nil {
t.Fatal(err)
}
if off != s.size {
t.Fatal("expected to seek to the end")
}
// shouldn't have actually seeked.
b, err := io.ReadAll(s)
if err != nil {
t.Fatal(err)
}
if len(b) != 0 {
t.Fatal("expected to read nothing")
}
// shouldn't need to actually seek.
off, err = s.Seek(0, io.SeekStart)
if err != nil {
t.Fatal(err)
}
if off != 0 {
t.Fatal("expected to seek to the start")
}
b, err = io.ReadAll(s)
if err != nil {
t.Fatal(err)
}
if string(b) != "fubar" {
t.Fatal("expected to read string")
}
// should fail the second time.
off, err = s.Seek(0, io.SeekStart)
if err != nil {
t.Fatal(err)
}
if off != 0 {
t.Fatal("expected to seek to the start")
}
// right here...
b, err = io.ReadAll(s)
if err == nil {
t.Fatalf("expected an error, got output %s", string(b))
}
if err != errBadSeek {
t.Fatalf("expected a bad seek error, got %s", err)
}
if len(b) != 0 {
t.Fatalf("expected to read nothing")
}
}
func TestLazySeeker(t *testing.T) {
underlyingBuffer := strings.NewReader("fubar")
s := &lazySeeker{
reader: underlyingBuffer,
size: underlyingBuffer.Size(),
}
expectByte := func(b byte) {
t.Helper()
var buf [1]byte
n, err := io.ReadFull(s, buf[:])
if err != nil {
t.Fatal(err)
}
if n != 1 {
t.Fatalf("expected to read one byte, read %d", n)
}
if buf[0] != b {
t.Fatalf("expected %b, got %b", b, buf[0])
}
}
expectSeek := func(whence int, off, expOff int64, expErr string) {
t.Helper()
n, err := s.Seek(off, whence)
if expErr == "" {
if err != nil {
t.Fatal("unexpected seek error: ", err)
}
} else {
if err == nil || err.Error() != expErr {
t.Fatalf("expected %s, got %s", err, expErr)
}
}
if n != expOff {
t.Fatalf("expected offset %d, got, %d", expOff, n)
}
}
expectSeek(io.SeekEnd, 0, s.size, "")
b, err := io.ReadAll(s)
if err != nil {
t.Fatal(err)
}
if len(b) != 0 {
t.Fatal("expected to read nothing")
}
expectSeek(io.SeekEnd, -1, s.size-1, "")
expectByte('r')
expectSeek(io.SeekStart, 0, 0, "")
expectByte('f')
expectSeek(io.SeekCurrent, 1, 2, "")
expectByte('b')
expectSeek(io.SeekCurrent, -100, 3, "invalid seek offset")
}

View File

@ -1,11 +1,13 @@
package corehttp
// TODO: move to IPNS
const WebUIPath = "/ipfs/bafybeibjbq3tmmy7wuihhhwvbladjsd3gx3kfjepxzkq6wylik6wc3whzy" // v2.20.0
const WebUIPath = "/ipfs/bafybeifeqt7mvxaniphyu2i3qhovjaf3sayooxbh5enfdqtiehxjv2ldte" // v2.22.0
// WebUIPaths is a list of all past webUI paths.
var WebUIPaths = []string{
WebUIPath,
"/ipfs/bafybeiequgo72mrvuml56j4gk7crewig5bavumrrzhkqbim6b3s2yqi7ty",
"/ipfs/bafybeibjbq3tmmy7wuihhhwvbladjsd3gx3kfjepxzkq6wylik6wc3whzy",
"/ipfs/bafybeiavrvt53fks6u32n5p2morgblcmck4bh4ymf4rrwu7ah5zsykmqqa",
"/ipfs/bafybeiageaoxg6d7npaof6eyzqbwvbubyler7bq44hayik2hvqcggg7d2y",
"/ipfs/bafybeidb5eryh72zajiokdggzo7yct2d6hhcflncji5im2y5w26uuygdsm",

View File

@ -11,10 +11,10 @@ import (
"github.com/ipfs/go-cid"
bstore "github.com/ipfs/go-ipfs-blockstore"
chunker "github.com/ipfs/go-ipfs-chunker"
files "github.com/ipfs/go-ipfs-files"
pin "github.com/ipfs/go-ipfs-pinner"
posinfo "github.com/ipfs/go-ipfs-posinfo"
ipld "github.com/ipfs/go-ipld-format"
"github.com/ipfs/go-libipfs/files"
logging "github.com/ipfs/go-log"
dag "github.com/ipfs/go-merkledag"
"github.com/ipfs/go-mfs"

View File

@ -14,14 +14,14 @@ import (
"github.com/ipfs/kubo/gc"
"github.com/ipfs/kubo/repo"
blocks "github.com/ipfs/go-block-format"
"github.com/ipfs/go-blockservice"
"github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore"
syncds "github.com/ipfs/go-datastore/sync"
blockstore "github.com/ipfs/go-ipfs-blockstore"
files "github.com/ipfs/go-ipfs-files"
pi "github.com/ipfs/go-ipfs-posinfo"
blocks "github.com/ipfs/go-libipfs/blocks"
"github.com/ipfs/go-libipfs/files"
dag "github.com/ipfs/go-merkledag"
coreiface "github.com/ipfs/interface-go-ipfs-core"
config "github.com/ipfs/kubo/config"

View File

@ -4,10 +4,10 @@ import (
"context"
"time"
"github.com/ipfs/go-bitswap"
"github.com/ipfs/go-bitswap/network"
blockstore "github.com/ipfs/go-ipfs-blockstore"
exchange "github.com/ipfs/go-ipfs-exchange-interface"
"github.com/ipfs/go-libipfs/bitswap"
"github.com/ipfs/go-libipfs/bitswap/network"
"github.com/ipfs/kubo/config"
irouting "github.com/ipfs/kubo/routing"
"github.com/libp2p/go-libp2p/core/host"

View File

@ -11,6 +11,7 @@ import (
"github.com/ipfs/go-log"
"github.com/ipfs/kubo/config"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p-pubsub/timecache"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/ipfs/kubo/core/node/libp2p"
@ -66,6 +67,18 @@ func LibP2P(bcfg *BuildCfg, cfg *config.Config) fx.Option {
pubsub.WithSeenMessagesTTL(cfg.Pubsub.SeenMessagesTTL.WithDefault(pubsub.TimeCacheDuration)),
)
var seenMessagesStrategy timecache.Strategy
configSeenMessagesStrategy := cfg.Pubsub.SeenMessagesStrategy.WithDefault(config.DefaultSeenMessagesStrategy)
switch configSeenMessagesStrategy {
case config.LastSeenMessagesStrategy:
seenMessagesStrategy = timecache.Strategy_LastSeen
case config.FirstSeenMessagesStrategy:
seenMessagesStrategy = timecache.Strategy_FirstSeen
default:
return fx.Error(fmt.Errorf("unsupported Pubsub.SeenMessagesStrategy %q", configSeenMessagesStrategy))
}
pubsubOptions = append(pubsubOptions, pubsub.WithSeenMessagesStrategy(seenMessagesStrategy))
switch cfg.Pubsub.Router {
case "":
fallthrough
@ -291,7 +304,12 @@ func Online(bcfg *BuildCfg, cfg *config.Config) fx.Option {
fx.Provide(p2p.New),
LibP2P(bcfg, cfg),
OnlineProviders(cfg.Experimental.StrategicProviding, cfg.Experimental.AcceleratedDHTClient, cfg.Reprovider.Strategy, cfg.Reprovider.Interval),
OnlineProviders(
cfg.Experimental.StrategicProviding,
cfg.Experimental.AcceleratedDHTClient,
cfg.Reprovider.Strategy.WithDefault(config.DefaultReproviderStrategy),
cfg.Reprovider.Interval.WithDefault(config.DefaultReproviderInterval),
),
)
}
@ -304,7 +322,12 @@ func Offline(cfg *config.Config) fx.Option {
fx.Provide(libp2p.Routing),
fx.Provide(libp2p.ContentRouting),
fx.Provide(libp2p.OfflineRouting),
OfflineProviders(cfg.Experimental.StrategicProviding, cfg.Experimental.AcceleratedDHTClient, cfg.Reprovider.Strategy, cfg.Reprovider.Interval),
OfflineProviders(
cfg.Experimental.StrategicProviding,
cfg.Experimental.AcceleratedDHTClient,
cfg.Reprovider.Strategy.WithDefault(config.DefaultReproviderStrategy),
cfg.Reprovider.Interval.WithDefault(config.DefaultReproviderInterval),
),
)
}

View File

@ -19,7 +19,7 @@ import (
"go.opencensus.io/stats/view"
"go.uber.org/fx"
config "github.com/ipfs/kubo/config"
"github.com/ipfs/kubo/config"
"github.com/ipfs/kubo/core/node/helpers"
"github.com/ipfs/kubo/repo"
)
@ -52,7 +52,8 @@ func ResourceManager(cfg config.SwarmConfig) interface{} {
return nil, opts, fmt.Errorf("opening IPFS_PATH: %w", err)
}
limitConfig, err := createDefaultLimitConfig(cfg)
var limitConfig rcmgr.LimitConfig
defaultComputedLimitConfig, err := createDefaultLimitConfig(cfg)
if err != nil {
return nil, opts, err
}
@ -61,10 +62,19 @@ func ResourceManager(cfg config.SwarmConfig) interface{} {
// is documented in docs/config.md.
// Any changes here should be reflected there.
if cfg.ResourceMgr.Limits != nil {
l := *cfg.ResourceMgr.Limits
// This effectively overrides the computed default LimitConfig with any vlues from cfg.ResourceMgr.Limits
l.Apply(limitConfig)
limitConfig = l
userSuppliedOverrideLimitConfig := *cfg.ResourceMgr.Limits
// This effectively overrides the computed default LimitConfig with any non-zero values from cfg.ResourceMgr.Limits.
// Because of how how Apply works, any 0 value for a user supplied override
// will be overriden with a computed default value.
// There currently isn't a way for a user to supply a 0-value override.
userSuppliedOverrideLimitConfig.Apply(defaultComputedLimitConfig)
limitConfig = userSuppliedOverrideLimitConfig
} else {
limitConfig = defaultComputedLimitConfig
}
if err := ensureConnMgrMakeSenseVsResourceMgr(limitConfig, cfg.ConnMgr); err != nil {
return nil, opts, err
}
limiter := rcmgr.NewFixedLimiter(limitConfig)
@ -113,7 +123,7 @@ func ResourceManager(cfg config.SwarmConfig) interface{} {
manager = lrm
} else {
fmt.Println("go-libp2p resource manager protection disabled")
manager = network.NullResourceManager
manager = &network.NullResourceManager{}
}
opts.Opts = append(opts.Opts, libp2p.ResourceManager(manager))
@ -598,3 +608,41 @@ func NetResetLimit(mgr network.ResourceManager, repo repo.Repo, scope string) (r
return result, nil
}
func ensureConnMgrMakeSenseVsResourceMgr(rcm rcmgr.LimitConfig, cmgr config.ConnMgr) error {
if cmgr.Type.WithDefault(config.DefaultConnMgrType) == "none" {
return nil // none connmgr, no checks to do
}
highWater := cmgr.HighWater.WithDefault(config.DefaultConnMgrHighWater)
if rcm.System.ConnsInbound <= rcm.System.Conns {
if int64(rcm.System.ConnsInbound) <= highWater {
// nolint
return fmt.Errorf(`
Unable to initialize libp2p due to conflicting limit configuration:
ResourceMgr.Limits.System.ConnsInbound (%d) must be bigger than ConnMgr.HighWater (%d)
`, rcm.System.ConnsInbound, highWater)
}
} else if int64(rcm.System.Conns) <= highWater {
// nolint
return fmt.Errorf(`
Unable to initialize libp2p due to conflicting limit configuration:
ResourceMgr.Limits.System.Conns (%d) must be bigger than ConnMgr.HighWater (%d)
`, rcm.System.Conns, highWater)
}
if rcm.System.StreamsInbound <= rcm.System.Streams {
if int64(rcm.System.StreamsInbound) <= highWater {
// nolint
return fmt.Errorf(`
Unable to initialize libp2p due to conflicting limit configuration:
ResourceMgr.Limits.System.StreamsInbound (%d) must be bigger than ConnMgr.HighWater (%d)
`, rcm.System.StreamsInbound, highWater)
}
} else if int64(rcm.System.Streams) <= highWater {
// nolint
return fmt.Errorf(`
Unable to initialize libp2p due to conflicting limit configuration:
ResourceMgr.Limits.System.Streams (%d) must be bigger than ConnMgr.HighWater (%d)
`, rcm.System.Streams, highWater)
}
return nil
}

View File

@ -44,17 +44,18 @@ var noLimitIncrease = rcmgr.BaseLimitIncrease{
// This file defines implicit limit defaults used when Swarm.ResourceMgr.Enabled
// createDefaultLimitConfig creates LimitConfig to pass to libp2p's resource manager.
// The defaults follow the documentation in docs/config.md.
// The defaults follow the documentation in docs/libp2p-resource-management.md.
// Any changes in the logic here should be reflected there.
func createDefaultLimitConfig(cfg config.SwarmConfig) (rcmgr.LimitConfig, error) {
maxMemoryDefaultString := humanize.Bytes(uint64(memory.TotalMemory()) / 4)
maxMemoryDefaultString := humanize.Bytes(uint64(memory.TotalMemory()) / 2)
maxMemoryString := cfg.ResourceMgr.MaxMemory.WithDefault(maxMemoryDefaultString)
maxMemory, err := humanize.ParseBytes(maxMemoryString)
if err != nil {
return rcmgr.LimitConfig{}, err
}
numFD := cfg.ResourceMgr.MaxFileDescriptors.WithDefault(int64(fd.GetNumFDs()) / 2)
maxMemoryMB := maxMemory / (1024 * 1024)
maxFD := int(cfg.ResourceMgr.MaxFileDescriptors.WithDefault(int64(fd.GetNumFDs()) / 2))
// We want to see this message on startup, that's why we are using fmt instead of log.
fmt.Printf(`
@ -65,65 +66,53 @@ Computing default go-libp2p Resource Manager limits based on:
Applying any user-supplied overrides on top.
Run 'ipfs swarm limit all' to see the resulting limits.
`, maxMemoryString, numFD)
`, maxMemoryString, maxFD)
// At least as of 2023-01-25, it's possible to open a connection that
// doesn't ask for any memory usage with the libp2p Resource Manager/Accountant
// (see https://github.com/libp2p/go-libp2p/issues/2010#issuecomment-1404280736).
// As a result, we can't curretly rely on Memory limits to full protect us.
// Until https://github.com/libp2p/go-libp2p/issues/2010 is addressed,
// we take a proxy now of restricting to 1 inbound connection per MB.
// Note: this is more generous than go-libp2p's default autoscaled limits which do
// 64 connections per 1GB
// (see https://github.com/libp2p/go-libp2p/blob/master/p2p/host/resource-manager/limit_defaults.go#L357 ).
systemConnsInbound := int(1 * maxMemoryMB)
scalingLimitConfig := rcmgr.ScalingLimitConfig{
SystemBaseLimit: rcmgr.BaseLimit{
Memory: int64(maxMemory),
FD: int(numFD),
FD: maxFD,
// By default, we just limit connections on the inbound side.
Conns: bigEnough,
ConnsInbound: rcmgr.DefaultLimits.SystemBaseLimit.ConnsInbound, // same as libp2p default
ConnsInbound: systemConnsInbound,
ConnsOutbound: bigEnough,
// We limit streams since they not only take up memory and CPU.
// The Memory limit protects us on the memory side,
// but a StreamsInbound limit helps protect against unbound CPU consumption from stream processing.
Streams: bigEnough,
StreamsInbound: rcmgr.DefaultLimits.SystemBaseLimit.StreamsInbound,
StreamsInbound: bigEnough,
StreamsOutbound: bigEnough,
},
// Most limits don't see an increase because they're already infinite/bigEnough or at their max value.
// The values that should scale based on the amount of memory allocated to libp2p need to increase accordingly.
SystemLimitIncrease: rcmgr.BaseLimitIncrease{
Memory: 0,
FDFraction: 0,
Conns: 0,
ConnsInbound: rcmgr.DefaultLimits.SystemLimitIncrease.ConnsInbound,
ConnsOutbound: 0,
Streams: 0,
StreamsInbound: rcmgr.DefaultLimits.SystemLimitIncrease.StreamsInbound,
StreamsOutbound: 0,
},
SystemLimitIncrease: noLimitIncrease,
// Transient connections won't cause any memory to accounted for by the resource manager.
// Only established connections do.
// As a result, we can't rely on System.Memory to protect us from a bunch of transient connection being opened.
// We limit the same values as the System scope, but only allow the Transient scope to take 25% of what is allowed for the System scope.
TransientBaseLimit: rcmgr.BaseLimit{
Memory: rcmgr.DefaultLimits.TransientBaseLimit.Memory,
FD: rcmgr.DefaultLimits.TransientBaseLimit.FD,
Memory: int64(maxMemory / 4),
FD: maxFD / 4,
Conns: bigEnough,
ConnsInbound: rcmgr.DefaultLimits.TransientBaseLimit.ConnsInbound,
ConnsInbound: systemConnsInbound / 4,
ConnsOutbound: bigEnough,
Streams: bigEnough,
StreamsInbound: rcmgr.DefaultLimits.TransientBaseLimit.StreamsInbound,
StreamsInbound: bigEnough,
StreamsOutbound: bigEnough,
},
TransientLimitIncrease: rcmgr.BaseLimitIncrease{
Memory: rcmgr.DefaultLimits.TransientLimitIncrease.Memory,
FDFraction: rcmgr.DefaultLimits.TransientLimitIncrease.FDFraction,
Conns: 0,
ConnsInbound: rcmgr.DefaultLimits.TransientLimitIncrease.ConnsInbound,
ConnsOutbound: 0,
Streams: 0,
StreamsInbound: rcmgr.DefaultLimits.TransientLimitIncrease.StreamsInbound,
StreamsOutbound: 0,
},
TransientLimitIncrease: noLimitIncrease,
// Lets get out of the way of the allow list functionality.
// If someone specified "Swarm.ResourceMgr.Allowlist" we should let it go through.
@ -184,7 +173,26 @@ Run 'ipfs swarm limit all' to see the resulting limits.
// Whatever limits libp2p has specifically tuned for its protocols/services we'll apply.
libp2p.SetDefaultServiceLimits(&scalingLimitConfig)
defaultLimitConfig := scalingLimitConfig.Scale(int64(maxMemory), int(numFD))
defaultLimitConfig := scalingLimitConfig.Scale(int64(maxMemory), maxFD)
// Simple checks to overide autoscaling ensuring limits make sense versus the connmgr values.
// There are ways to break this, but this should catch most problems already.
// We might improve this in the future.
// See: https://github.com/ipfs/kubo/issues/9545
if cfg.ConnMgr.Type.WithDefault(config.DefaultConnMgrType) != "none" {
maxInboundConns := int64(defaultLimitConfig.System.ConnsInbound)
if connmgrHighWaterTimesTwo := cfg.ConnMgr.HighWater.WithDefault(config.DefaultConnMgrHighWater) * 2; maxInboundConns < connmgrHighWaterTimesTwo {
maxInboundConns = connmgrHighWaterTimesTwo
}
if maxInboundConns < config.DefaultResourceMgrMinInboundConns {
maxInboundConns = config.DefaultResourceMgrMinInboundConns
}
// Scale System.StreamsInbound as well, but use the existing ratio of StreamsInbound to ConnsInbound
defaultLimitConfig.System.StreamsInbound = int(maxInboundConns * int64(defaultLimitConfig.System.StreamsInbound) / int64(defaultLimitConfig.System.ConnsInbound))
defaultLimitConfig.System.ConnsInbound = int(maxInboundConns)
}
return defaultLimitConfig, nil
}

View File

@ -50,11 +50,11 @@ func (n *loggingResourceManager) start(ctx context.Context) {
n.limitExceededErrs = make(map[string]int)
for e, count := range errs {
n.logger.Warnf("Protected from exceeding resource limits %d times: %q.", count, e)
n.logger.Warnf("Protected from exceeding resource limits %d times. libp2p message: %q.", count, e)
}
if len(errs) != 0 {
n.logger.Warnf("Consider inspecting logs and raising the resource manager limits. Documentation: https://github.com/ipfs/kubo/blob/master/docs/config.md#swarmresourcemgr")
n.logger.Warnf("Learn more about potential actions to take at: https://github.com/ipfs/kubo/blob/master/docs/libp2p-resource-management.md")
}
n.mut.Unlock()

View File

@ -55,7 +55,7 @@ func TestLoggingResourceManager(t *testing.T) {
if oLogs.Len() == 0 {
continue
}
require.Equal(t, "Protected from exceeding resource limits 2 times: \"system: cannot reserve inbound connection: resource limit exceeded\".", oLogs.All()[0].Message)
require.Equal(t, "Protected from exceeding resource limits 2 times. libp2p message: \"system: cannot reserve inbound connection: resource limit exceeded\".", oLogs.All()[0].Message)
return
}
}

View File

@ -7,13 +7,9 @@ import (
"sort"
"time"
"github.com/ipfs/kubo/core/node/helpers"
irouting "github.com/ipfs/kubo/routing"
"github.com/cenkalti/backoff/v4"
ds "github.com/ipfs/go-datastore"
offroute "github.com/ipfs/go-ipfs-routing/offline"
config "github.com/ipfs/kubo/config"
"github.com/ipfs/kubo/repo"
dht "github.com/libp2p/go-libp2p-kad-dht"
ddht "github.com/libp2p/go-libp2p-kad-dht/dual"
"github.com/libp2p/go-libp2p-kad-dht/fullrt"
@ -24,9 +20,12 @@ import (
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/routing"
"github.com/cenkalti/backoff/v4"
"go.uber.org/fx"
config "github.com/ipfs/kubo/config"
"github.com/ipfs/kubo/core/node/helpers"
"github.com/ipfs/kubo/repo"
irouting "github.com/ipfs/kubo/routing"
)
type Router struct {
@ -77,6 +76,21 @@ func BaseRouting(experimentalDHTClient bool) interface{} {
})
}
if pr, ok := in.Router.(routinghelpers.ComposableRouter); ok {
for _, r := range pr.Routers() {
if dht, ok := r.(*ddht.DHT); ok {
dr = dht
lc.Append(fx.Hook{
OnStop: func(ctx context.Context) error {
return dr.Close()
},
})
break
}
}
}
if dr != nil && experimentalDHTClient {
cfg, err := in.Repo.Config()
if err != nil {

View File

@ -2,6 +2,9 @@ package libp2p
import (
"context"
"os"
"strings"
"time"
"github.com/ipfs/go-datastore"
"github.com/ipfs/kubo/config"
@ -23,6 +26,79 @@ type RoutingOption func(
...peer.AddrInfo,
) (routing.Routing, error)
// Default HTTP routers used in parallel to DHT when Routing.Type = "auto"
var defaultHTTPRouters = []string{
"https://cid.contact", // https://github.com/ipfs/kubo/issues/9422#issuecomment-1338142084
// TODO: add an independent router from Cloudflare
}
func init() {
// Override HTTP routers if custom ones were passed via env
if routers := os.Getenv("IPFS_HTTP_ROUTERS"); routers != "" {
defaultHTTPRouters = strings.Split(routers, " ")
}
}
// ConstructDefaultRouting returns routers used when Routing.Type is unset or set to "auto"
func ConstructDefaultRouting(peerID string, addrs []string, privKey string) func(
ctx context.Context,
host host.Host,
dstore datastore.Batching,
validator record.Validator,
bootstrapPeers ...peer.AddrInfo,
) (routing.Routing, error) {
return func(
ctx context.Context,
host host.Host,
dstore datastore.Batching,
validator record.Validator,
bootstrapPeers ...peer.AddrInfo,
) (routing.Routing, error) {
// Defined routers will be queried in parallel (optimizing for response speed)
// Different trade-offs can be made by setting Routing.Type = "custom" with own Routing.Routers
var routers []*routinghelpers.ParallelRouter
// Run the default DHT routing (same as Routing.Type = "dht")
dhtRouting, err := DHTOption(ctx, host, dstore, validator, bootstrapPeers...)
if err != nil {
return nil, err
}
routers = append(routers, &routinghelpers.ParallelRouter{
Router: dhtRouting,
IgnoreError: false,
Timeout: 5 * time.Minute, // https://github.com/ipfs/kubo/pull/9475#discussion_r1042501333
ExecuteAfter: 0,
})
// Append HTTP routers for additional speed
for _, endpoint := range defaultHTTPRouters {
httpRouter, err := irouting.ConstructHTTPRouter(endpoint, peerID, addrs, privKey)
if err != nil {
return nil, err
}
r := &irouting.Composer{
GetValueRouter: routinghelpers.Null{},
PutValueRouter: routinghelpers.Null{},
ProvideRouter: routinghelpers.Null{}, // modify this when indexers supports provide
FindPeersRouter: routinghelpers.Null{},
FindProvidersRouter: httpRouter,
}
routers = append(routers, &routinghelpers.ParallelRouter{
Router: r,
IgnoreError: true, // https://github.com/ipfs/kubo/pull/9475#discussion_r1042507387
Timeout: 15 * time.Second, // 5x server value from https://github.com/ipfs/kubo/pull/9475#discussion_r1042428529
ExecuteAfter: 0,
})
}
routing := routinghelpers.NewComposableParallel(routers)
return routing, nil
}
}
// constructDHTRouting is used when Routing.Type = "dht"
func constructDHTRouting(mode dht.ModeOpt) func(
ctx context.Context,
host host.Host,
@ -49,6 +125,7 @@ func constructDHTRouting(mode dht.ModeOpt) func(
}
}
// ConstructDelegatedRouting is used when Routing.Type = "custom"
func ConstructDelegatedRouting(routers config.Routers, methods config.Methods, peerID string, addrs []string, privKey string) func(
ctx context.Context,
host host.Host,

View File

@ -19,7 +19,6 @@ func yamuxTransport() network.Multiplexer {
if os.Getenv("YAMUX_DEBUG") != "" {
tpt.LogOutput = os.Stderr
}
return &tpt
}
@ -27,9 +26,6 @@ func makeSmuxTransportOption(tptConfig config.Transports) (libp2p.Option, error)
const yamuxID = "/yamux/1.0.0"
const mplexID = "/mplex/6.7.0"
ymxtpt := *yamux.DefaultTransport
ymxtpt.AcceptBacklog = 512
if prefs := os.Getenv("LIBP2P_MUX_PREFS"); prefs != "" {
// Using legacy LIBP2P_MUX_PREFS variable.
log.Error("LIBP2P_MUX_PREFS is now deprecated.")
@ -47,7 +43,7 @@ func makeSmuxTransportOption(tptConfig config.Transports) (libp2p.Option, error)
}
switch tpt {
case yamuxID:
opts = append(opts, libp2p.Muxer(tpt, yamuxTransport))
opts = append(opts, libp2p.Muxer(tpt, yamuxTransport()))
case mplexID:
opts = append(opts, libp2p.Muxer(tpt, mplex.DefaultTransport))
default:
@ -59,7 +55,7 @@ func makeSmuxTransportOption(tptConfig config.Transports) (libp2p.Option, error)
return prioritizeOptions([]priorityOption{{
priority: tptConfig.Multiplexers.Yamux,
defaultPriority: 100,
opt: libp2p.Muxer(yamuxID, yamuxTransport),
opt: libp2p.Muxer(yamuxID, yamuxTransport()),
}, {
priority: tptConfig.Multiplexers.Mplex,
defaultPriority: 200,

View File

@ -36,12 +36,10 @@ func Transports(tptConfig config.Transports) interface{} {
"QUIC transport does not support private networks, please disable Swarm.Transports.Network.QUIC",
)
}
// TODO(9290): Make WithMetrics configurable
opts.Opts = append(opts.Opts, libp2p.Transport(quic.NewTransport, quic.WithMetrics()))
opts.Opts = append(opts.Opts, libp2p.Transport(quic.NewTransport))
}
// TODO(9292): Remove the false && to allows it enabled by default
if tptConfig.Network.WebTransport.WithDefault(false && !privateNetworkEnabled) {
if tptConfig.Network.WebTransport.WithDefault(!privateNetworkEnabled) {
if privateNetworkEnabled {
return opts, fmt.Errorf(
"WebTransport transport does not support private networks, please disable Swarm.Transports.Network.WebTransport",

View File

@ -18,8 +18,6 @@ import (
irouting "github.com/ipfs/kubo/routing"
)
const kReprovideFrequency = time.Hour * 12
// SIMPLE
// ProviderQueue creates new datastore backed provider queue
@ -61,20 +59,10 @@ func SimpleProviderSys(isOnline bool) interface{} {
}
// BatchedProviderSys creates new provider system
func BatchedProviderSys(isOnline bool, reprovideInterval string) interface{} {
func BatchedProviderSys(isOnline bool, reprovideInterval time.Duration) interface{} {
return func(lc fx.Lifecycle, cr irouting.ProvideManyRouter, q *q.Queue, keyProvider simple.KeyChanFunc, repo repo.Repo) (provider.System, error) {
reprovideIntervalDuration := kReprovideFrequency
if reprovideInterval != "" {
dur, err := time.ParseDuration(reprovideInterval)
if err != nil {
return nil, err
}
reprovideIntervalDuration = dur
}
sys, err := batched.New(cr, q,
batched.ReproviderInterval(reprovideIntervalDuration),
batched.ReproviderInterval(reprovideInterval),
batched.Datastore(repo.Datastore()),
batched.KeyProvider(keyProvider))
if err != nil {
@ -100,7 +88,7 @@ func BatchedProviderSys(isOnline bool, reprovideInterval string) interface{} {
// ONLINE/OFFLINE
// OnlineProviders groups units managing provider routing records online
func OnlineProviders(useStrategicProviding bool, useBatchedProviding bool, reprovideStrategy string, reprovideInterval string) fx.Option {
func OnlineProviders(useStrategicProviding bool, useBatchedProviding bool, reprovideStrategy string, reprovideInterval time.Duration) fx.Option {
if useStrategicProviding {
return fx.Provide(provider.NewOfflineProvider)
}
@ -113,7 +101,7 @@ func OnlineProviders(useStrategicProviding bool, useBatchedProviding bool, repro
}
// OfflineProviders groups units managing provider routing records offline
func OfflineProviders(useStrategicProviding bool, useBatchedProviding bool, reprovideStrategy string, reprovideInterval string) fx.Option {
func OfflineProviders(useStrategicProviding bool, useBatchedProviding bool, reprovideStrategy string, reprovideInterval time.Duration) fx.Option {
if useStrategicProviding {
return fx.Provide(provider.NewOfflineProvider)
}
@ -126,17 +114,7 @@ func OfflineProviders(useStrategicProviding bool, useBatchedProviding bool, repr
}
// SimpleProviders creates the simple provider/reprovider dependencies
func SimpleProviders(reprovideStrategy string, reprovideInterval string) fx.Option {
reproviderInterval := kReprovideFrequency
if reprovideInterval != "" {
dur, err := time.ParseDuration(reprovideInterval)
if err != nil {
return fx.Error(err)
}
reproviderInterval = dur
}
func SimpleProviders(reprovideStrategy string, reproviderInterval time.Duration) fx.Option {
var keyProvider fx.Option
switch reprovideStrategy {
case "all":

View File

@ -2,7 +2,7 @@ include mk/header.mk
GOCC ?= go
$(d)/coverage_deps: $$(DEPS_GO)
$(d)/coverage_deps: $$(DEPS_GO) cmd/ipfs/ipfs
rm -rf $(@D)/unitcover && mkdir $(@D)/unitcover
rm -rf $(@D)/sharnesscover && mkdir $(@D)/sharnesscover
@ -46,7 +46,7 @@ endif
export IPFS_COVER_DIR:= $(realpath $(d))/sharnesscover/
$(d)/sharness_tests.coverprofile: export TEST_NO_PLUGIN=1
$(d)/sharness_tests.coverprofile: $(d)/ipfs cmd/ipfs/ipfs-test-cover $(d)/coverage_deps test_sharness_short
$(d)/sharness_tests.coverprofile: $(d)/ipfs cmd/ipfs/ipfs-test-cover $(d)/coverage_deps test_sharness
(cd $(@D)/sharnesscover && find . -type f | gocovmerge -list -) > $@

View File

@ -27,7 +27,6 @@ We will ask early testers to participate at two points in the process:
- [ ] Textile (@sanderpick)
- [ ] Pinata (@obo20)
- [ ] RTrade (@postables)
- [ ] QRI (@b5)
- [ ] Siderus (@koalalorenzo)
- [ ] Charity Engine (@rytiss, @tristanolive)
- [ ] Fission (@bmann)

View File

@ -1,11 +1,17 @@
# Developer Documentation and Guides
If you are looking for User Documentation & Guides, please visit [docs.ipfs.tech](https://docs.ipfs.tech/).
If you are looking for User Documentation & Guides, please visit [docs.ipfs.tech](https://docs.ipfs.tech/) or check [General Documentation](#general-documentation).
If youre experiencing an issue with IPFS, **please follow [our issue guide](github-issue-guide.md) when filing an issue!**
Otherwise, check out the following guides to using and developing IPFS:
## General Documentation
- [Configuration reference](config.md)
- [Datastore configuration](datastores.md)
- [Experimental features](experimental-features.md)
## Developing `kubo`
- First, please read the Contributing Guidelines [for IPFS projects](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) and then the Contributing Guidelines for [Go code specifically](https://github.com/ipfs/community/blob/master/CONTRIBUTING_GO.md)
@ -22,9 +28,6 @@ Otherwise, check out the following guides to using and developing IPFS:
## Advanced User Guides
- [Transferring a File Over IPFS](file-transfer.md)
- [Configuration reference](config.md)
- [Datastore configuration](datastores.md)
- [Experimental features](experimental-features.md)
- [Installing command completion](command-completion.md)
- [Mounting IPFS with FUSE](fuse.md)
- [Installing plugins](plugins.md)

Some files were not shown because too many files have changed in this diff Show More