mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2025-04-28 22:25:07 +03:00
Compare commits
240 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
47818a1a7c | ||
![]() |
6377d7f071 | ||
![]() |
5b8dbc8b1e | ||
![]() |
73705ff09d | ||
![]() |
3b18909f70 | ||
![]() |
58b727d1f0 | ||
![]() |
782c0250d7 | ||
![]() |
213f72b840 | ||
![]() |
1fbcf3b3c2 | ||
![]() |
22bc9c44e2 | ||
![]() |
9c73bacab9 | ||
![]() |
04be129878 | ||
![]() |
657f7e0db3 | ||
![]() |
7adf5f18b7 | ||
![]() |
69451fe969 | ||
![]() |
2d587740c1 | ||
![]() |
b2b0396d48 | ||
![]() |
83ec58afc7 | ||
![]() |
b436052b2d | ||
![]() |
3ed4a92288 | ||
![]() |
bdb2d399c5 | ||
![]() |
7790a19e4c | ||
![]() |
d3b4de46ea | ||
![]() |
2454970e4d | ||
![]() |
b98f98318f | ||
![]() |
ff9e90c5aa | ||
![]() |
9398cae230 | ||
![]() |
c22a746a1d | ||
![]() |
67ec5a92b3 | ||
![]() |
42873be09b | ||
![]() |
75d2080e53 | ||
![]() |
834680045a | ||
![]() |
eef613993f | ||
![]() |
ff0ef7ff56 | ||
![]() |
ef110b0181 | ||
![]() |
b20ad846a1 | ||
![]() |
0b9c8bd020 | ||
![]() |
0b9469100c | ||
![]() |
a6429390da | ||
![]() |
1ee61dcefa | ||
![]() |
81e345c1ae | ||
![]() |
a038a6a8ef | ||
![]() |
01e73792fe | ||
![]() |
d22dc9ecc9 | ||
![]() |
874083da79 | ||
![]() |
ccda1075c0 | ||
![]() |
6d5243bd9a | ||
![]() |
377bc664c9 | ||
![]() |
d1b849588f | ||
![]() |
d6fd305f12 | ||
![]() |
98a6fdb4f2 | ||
![]() |
c00779c7d3 | ||
![]() |
43a1a3de64 | ||
![]() |
b8ab843a98 | ||
![]() |
e138fa679c | ||
![]() |
361b9fd6fc | ||
![]() |
5461bb380e | ||
![]() |
34f087de1c | ||
![]() |
c4b29b735c | ||
![]() |
947b6ad7aa | ||
![]() |
340cedbe14 | ||
![]() |
b1283e15f6 | ||
![]() |
ef989bef63 | ||
![]() |
af9ff34995 | ||
![]() |
63cd757525 | ||
![]() |
5e5de3a343 | ||
![]() |
edf179ed26 | ||
![]() |
9950d1225d | ||
![]() |
4fbdeb4e3f | ||
![]() |
5ea16e63a1 | ||
![]() |
da7ebde828 | ||
![]() |
02d92ff81c | ||
![]() |
04c0acf71b | ||
![]() |
8ecc402d7c | ||
![]() |
c505097be0 | ||
![]() |
fec96a38a4 | ||
![]() |
f788a18bef | ||
![]() |
fcefb20993 | ||
![]() |
2831d73f73 | ||
![]() |
c2811c0cdc | ||
![]() |
5d9c5b3c9b | ||
![]() |
f56f9c124c | ||
![]() |
5da1fbe397 | ||
![]() |
6f3a0a71d4 | ||
![]() |
6cbe56adfe | ||
![]() |
2d644eabc3 | ||
![]() |
2c20a04369 | ||
![]() |
81f2c711b4 | ||
![]() |
180d7bf499 | ||
![]() |
9f4c89acad | ||
![]() |
5da4c1131e | ||
![]() |
768278a8e6 | ||
![]() |
1e9a59edf9 | ||
![]() |
3dfa6d0cc9 | ||
![]() |
6b6cd0bed5 | ||
![]() |
3d15da34ad | ||
![]() |
741f825b8e | ||
![]() |
676ae52503 | ||
![]() |
fef553ed18 | ||
![]() |
f6f669617f | ||
![]() |
39c4b24395 | ||
![]() |
0d676c6a3b | ||
![]() |
a0b3897278 | ||
![]() |
abec2256ae | ||
![]() |
7aca869170 | ||
![]() |
b759683b76 | ||
![]() |
6677d70648 | ||
![]() |
7ac38e3e58 | ||
![]() |
49c424ef21 | ||
![]() |
0346af46da | ||
![]() |
93a5adfd18 | ||
![]() |
ddb75700a0 | ||
![]() |
ae997a5acb | ||
![]() |
6a9c90d3eb | ||
![]() |
41e045fe5b | ||
![]() |
e5e8c84d7c | ||
![]() |
e41b838d8f | ||
![]() |
7f9d4f3f6d | ||
![]() |
a6b316ef08 | ||
![]() |
d781fef760 | ||
![]() |
b332664acb | ||
![]() |
01c1498bd5 | ||
![]() |
0b578a637a | ||
![]() |
82c54f87ea | ||
![]() |
d17ac39789 | ||
![]() |
ea6ccf552f | ||
![]() |
1ac3d540e7 | ||
![]() |
6873fd44ff | ||
![]() |
8afa737a8d | ||
![]() |
7934158f5f | ||
![]() |
a60771344a | ||
![]() |
90c6288f7c | ||
![]() |
094f80f39c | ||
![]() |
955aa4af79 | ||
![]() |
73c6c25bd9 | ||
![]() |
80e56eafcd | ||
![]() |
6a9493757d | ||
![]() |
8ea20cd205 | ||
![]() |
a2dffeff33 | ||
![]() |
a2053b51fe | ||
![]() |
aceb037c57 | ||
![]() |
bcd80b043f | ||
![]() |
74ca02edfd | ||
![]() |
e110dd46fd | ||
![]() |
88b773cd0a | ||
![]() |
efb4b4635d | ||
![]() |
117e4b88f8 | ||
![]() |
4b48fd0b5f | ||
![]() |
854cd75f04 | ||
![]() |
4f656685ef | ||
![]() |
ed8ba584e2 | ||
![]() |
2a21241738 | ||
![]() |
45b773eade | ||
![]() |
6dc847de31 | ||
![]() |
bd7e699130 | ||
![]() |
268ffbfd14 | ||
![]() |
490c11c29e | ||
![]() |
991ea8b876 | ||
![]() |
68d1036de8 | ||
![]() |
fa3d943ba9 | ||
![]() |
9defa35c66 | ||
![]() |
c8b9aaeb67 | ||
![]() |
8f3ab1d83c | ||
![]() |
12a3a8c73b | ||
![]() |
6ab0639b82 | ||
![]() |
fbc5f62add | ||
![]() |
5b203ad8c5 | ||
![]() |
fe14981dda | ||
![]() |
63b214f6b7 | ||
![]() |
ff96740ac7 | ||
![]() |
7f94463332 | ||
![]() |
bcbabff80f | ||
![]() |
99dd8f85d3 | ||
![]() |
57d9a2399f | ||
![]() |
423fc248d2 | ||
![]() |
516fcce6b3 | ||
![]() |
d8dc6b2670 | ||
![]() |
109f59c7dc | ||
![]() |
002b984c04 | ||
![]() |
5e684550a8 | ||
![]() |
80724438c9 | ||
![]() |
b0f8d8af13 | ||
![]() |
31177f5a73 | ||
![]() |
c1ae9ea0d4 | ||
![]() |
f6c0d8406d | ||
![]() |
db9b57c052 | ||
![]() |
2eda59d9e4 | ||
![]() |
06ca8941c7 | ||
![]() |
8562b6b86e | ||
![]() |
e94985c583 | ||
![]() |
5a6f27e732 | ||
![]() |
8b5add5301 | ||
![]() |
52709696a5 | ||
![]() |
cb8333f9ff | ||
![]() |
333561f4e1 | ||
![]() |
2565cbf11b | ||
![]() |
19ca25538f | ||
![]() |
aff3201084 | ||
![]() |
c0188f5600 | ||
![]() |
e0b39b303f | ||
![]() |
5ba9dadc49 | ||
![]() |
6e338b6f89 | ||
![]() |
e290e744f4 | ||
![]() |
a233e775eb | ||
![]() |
6ac2fae845 | ||
![]() |
7b1635245f | ||
![]() |
a9ec3877b5 | ||
![]() |
7afa23be4c | ||
![]() |
c7ee7d9681 | ||
![]() |
ffb2f06992 | ||
![]() |
101189a9dc | ||
![]() |
c7ea223a9a | ||
![]() |
669e61af9a | ||
![]() |
5e95246c26 | ||
![]() |
1345960d5f | ||
![]() |
8696650958 | ||
![]() |
ebd3596c2c | ||
![]() |
e99c870d51 | ||
![]() |
abbe94fa80 | ||
![]() |
fc632c5caa | ||
![]() |
5b6d9d52f3 | ||
![]() |
5a243d5b95 | ||
![]() |
a148f4cfec | ||
![]() |
83c1a810b5 | ||
![]() |
1420ea5662 | ||
![]() |
a8f0ada7ee | ||
![]() |
1685b87a04 | ||
![]() |
9ee6c46b1d | ||
![]() |
3b0a819e68 | ||
![]() |
38736358dd | ||
![]() |
1dd1d0ab8c | ||
![]() |
6d6c408957 | ||
![]() |
783b4d3de6 | ||
![]() |
a6f742ee93 | ||
![]() |
4189053cfc | ||
![]() |
886281af7c | ||
![]() |
9cbc71bc8a | ||
![]() |
723097fbf6 | ||
![]() |
1adc88ec77 | ||
![]() |
110613b234 |
88 changed files with 4477 additions and 2394 deletions
54
.github/workflows/ci.yml
vendored
54
.github/workflows/ci.yml
vendored
|
@ -15,12 +15,12 @@ jobs:
|
||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: stable
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
args: --issues-exit-code=1
|
args: --issues-exit-code=1
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v2
|
||||||
|
@ -51,17 +51,17 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
goversion: ["1.17", "1.18", "1.19"]
|
goversion: ["1.22", "1.23", "1.24"]
|
||||||
|
|
||||||
name: Build & Test (Linux, Go ${{ matrix.goversion }})
|
name: Build & Test (Linux, Go ${{ matrix.goversion }})
|
||||||
needs: [lint]
|
needs: [lint]
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.goversion }}
|
go-version: ${{ matrix.goversion }}
|
||||||
|
|
||||||
|
@ -75,17 +75,17 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
goversion: ["1.17", "1.18", "1.19"]
|
goversion: ["1.22", "1.23", "1.24"]
|
||||||
|
|
||||||
name: Build & Test (Windows, Go ${{ matrix.goversion }})
|
name: Build & Test (Windows, Go ${{ matrix.goversion }})
|
||||||
needs: [lint]
|
needs: [lint]
|
||||||
|
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.goversion }}
|
go-version: ${{ matrix.goversion }}
|
||||||
|
|
||||||
|
@ -99,17 +99,17 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
goversion: ["1.17", "1.18", "1.19"]
|
goversion: ["1.22", "1.23", "1.24"]
|
||||||
|
|
||||||
name: Build & Test (macOS, Go ${{ matrix.goversion }})
|
name: Build & Test (macOS, Go ${{ matrix.goversion }})
|
||||||
needs: [lint]
|
needs: [lint]
|
||||||
|
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.goversion }}
|
go-version: ${{ matrix.goversion }}
|
||||||
|
|
||||||
|
@ -119,6 +119,32 @@ jobs:
|
||||||
- name: Unit tests
|
- name: Unit tests
|
||||||
run: go test -v ./...
|
run: go test -v ./...
|
||||||
|
|
||||||
|
build-freebsd:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
goversion: ["1.22", "1.23", "1.24"]
|
||||||
|
goos:
|
||||||
|
- freebsd
|
||||||
|
- openbsd
|
||||||
|
|
||||||
|
name: Build (Cross ${{ matrix.goos }}, Go ${{ matrix.goversion }})
|
||||||
|
needs: [lint]
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.goversion }}
|
||||||
|
|
||||||
|
- name: Build Yggdrasil
|
||||||
|
run: go build -v ./...
|
||||||
|
env:
|
||||||
|
GOOS: ${{ matrix.goos }}
|
||||||
|
|
||||||
tests-ok:
|
tests-ok:
|
||||||
name: All tests passed
|
name: All tests passed
|
||||||
needs: [lint, codeql, build-linux, build-windows, build-macos]
|
needs: [lint, codeql, build-linux, build-windows, build-macos]
|
||||||
|
|
41
.github/workflows/pkg.yml
vendored
41
.github/workflows/pkg.yml
vendored
|
@ -16,16 +16,16 @@ jobs:
|
||||||
|
|
||||||
name: Package (Debian, ${{ matrix.pkgarch }})
|
name: Package (Debian, ${{ matrix.pkgarch }})
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: "stable"
|
||||||
|
|
||||||
- name: Build package
|
- name: Build package
|
||||||
env:
|
env:
|
||||||
|
@ -33,7 +33,7 @@ jobs:
|
||||||
run: sh contrib/deb/generate.sh
|
run: sh contrib/deb/generate.sh
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Debian package (${{ matrix.pkgarch }})
|
name: Debian package (${{ matrix.pkgarch }})
|
||||||
path: "*.deb"
|
path: "*.deb"
|
||||||
|
@ -49,14 +49,14 @@ jobs:
|
||||||
|
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: "stable"
|
||||||
|
|
||||||
- name: Build package
|
- name: Build package
|
||||||
env:
|
env:
|
||||||
|
@ -64,7 +64,7 @@ jobs:
|
||||||
run: sh contrib/macos/create-pkg.sh
|
run: sh contrib/macos/create-pkg.sh
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: macOS package (${{ matrix.pkgarch }})
|
name: macOS package (${{ matrix.pkgarch }})
|
||||||
path: "*.pkg"
|
path: "*.pkg"
|
||||||
|
@ -80,20 +80,23 @@ jobs:
|
||||||
|
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: "stable"
|
||||||
|
|
||||||
|
- name: Setup .NET Core SDK
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
|
|
||||||
- name: Build package
|
- name: Build package
|
||||||
run: sh contrib/msi/build-msi.sh ${{ matrix.pkgarch }}
|
run: sh contrib/msi/build-msi.sh ${{ matrix.pkgarch }}
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Windows package (${{ matrix.pkgarch }})
|
name: Windows package (${{ matrix.pkgarch }})
|
||||||
path: "*.msi"
|
path: "*.msi"
|
||||||
|
@ -107,22 +110,22 @@ jobs:
|
||||||
|
|
||||||
name: Package (Router, ${{ matrix.pkgarch }})
|
name: Package (Router, ${{ matrix.pkgarch }})
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
path: yggdrasil
|
path: yggdrasil
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: neilalexander/vyatta-yggdrasil
|
repository: neilalexander/vyatta-yggdrasil
|
||||||
path: vyatta-yggdrasil
|
path: vyatta-yggdrasil
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: "stable"
|
||||||
|
|
||||||
- name: Build package
|
- name: Build package
|
||||||
env:
|
env:
|
||||||
|
@ -130,7 +133,7 @@ jobs:
|
||||||
run: cd /home/runner/work/yggdrasil-go/yggdrasil-go/vyatta-yggdrasil && ./build-${{ matrix.pkgarch }}
|
run: cd /home/runner/work/yggdrasil-go/yggdrasil-go/vyatta-yggdrasil && ./build-${{ matrix.pkgarch }}
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Router package (${{ matrix.pkgarch }})
|
name: Router package (${{ matrix.pkgarch }})
|
||||||
path: "/home/runner/work/yggdrasil-go/yggdrasil-go/vyatta-yggdrasil/*.deb"
|
path: "/home/runner/work/yggdrasil-go/yggdrasil-go/vyatta-yggdrasil/*.deb"
|
||||||
|
|
|
@ -2,7 +2,8 @@ run:
|
||||||
build-tags:
|
build-tags:
|
||||||
- lint
|
- lint
|
||||||
issues-exit-code: 0 # TODO: change this to 1 when we want it to fail builds
|
issues-exit-code: 0 # TODO: change this to 1 when we want it to fail builds
|
||||||
skip-dirs:
|
issues:
|
||||||
|
exclude-dirs:
|
||||||
- contrib/
|
- contrib/
|
||||||
- misc/
|
- misc/
|
||||||
linters:
|
linters:
|
||||||
|
|
942
CHANGELOG.md
942
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
@ -24,7 +24,7 @@ or tools in the `contrib` folder.
|
||||||
If you want to build from source, as opposed to installing one of the pre-built
|
If you want to build from source, as opposed to installing one of the pre-built
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
1. Install [Go](https://golang.org) (requires Go 1.17 or later)
|
1. Install [Go](https://golang.org) (requires Go 1.22 or later)
|
||||||
2. Clone this repository
|
2. Clone this repository
|
||||||
2. Run `./build`
|
2. Run `./build`
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
/*
|
/*
|
||||||
|
|
||||||
This file generates crypto keys.
|
This file generates crypto keys.
|
||||||
It prints out a new set of keys each time if finds a "better" one.
|
It prints out a new set of keys each time if finds a "better" one.
|
||||||
By default, "better" means a higher NodeID (-> higher IP address).
|
By default, "better" means a higher NodeID (-> higher IP address).
|
||||||
|
@ -8,7 +7,6 @@ This is because the IP address format can compress leading 1s in the address, to
|
||||||
If run with the "-sig" flag, it generates signing keys instead.
|
If run with the "-sig" flag, it generates signing keys instead.
|
||||||
A "better" signing key means one with a higher TreeID.
|
A "better" signing key means one with a higher TreeID.
|
||||||
This only matters if it's high enough to make you the root of the tree.
|
This only matters if it's high enough to make you the root of the tree.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
@ -18,6 +16,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"suah.dev/protect"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
)
|
)
|
||||||
|
@ -25,10 +26,19 @@ import (
|
||||||
type keySet struct {
|
type keySet struct {
|
||||||
priv ed25519.PrivateKey
|
priv ed25519.PrivateKey
|
||||||
pub ed25519.PublicKey
|
pub ed25519.PublicKey
|
||||||
|
count uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
if err := protect.Pledge("stdio"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
threads := runtime.GOMAXPROCS(0)
|
threads := runtime.GOMAXPROCS(0)
|
||||||
|
fmt.Println("Threads:", threads)
|
||||||
|
start := time.Now()
|
||||||
|
var totalKeys uint64
|
||||||
|
totalKeys = 0
|
||||||
var currentBest ed25519.PublicKey
|
var currentBest ed25519.PublicKey
|
||||||
newKeys := make(chan keySet, threads)
|
newKeys := make(chan keySet, threads)
|
||||||
for i := 0; i < threads; i++ {
|
for i := 0; i < threads; i++ {
|
||||||
|
@ -37,8 +47,9 @@ func main() {
|
||||||
for {
|
for {
|
||||||
newKey := <-newKeys
|
newKey := <-newKeys
|
||||||
if isBetter(currentBest, newKey.pub) || len(currentBest) == 0 {
|
if isBetter(currentBest, newKey.pub) || len(currentBest) == 0 {
|
||||||
|
totalKeys += newKey.count
|
||||||
currentBest = newKey.pub
|
currentBest = newKey.pub
|
||||||
fmt.Println("-----")
|
fmt.Println("-----", time.Since(start), "---", totalKeys, "keys tried")
|
||||||
fmt.Println("Priv:", hex.EncodeToString(newKey.priv))
|
fmt.Println("Priv:", hex.EncodeToString(newKey.priv))
|
||||||
fmt.Println("Pub:", hex.EncodeToString(newKey.pub))
|
fmt.Println("Pub:", hex.EncodeToString(newKey.pub))
|
||||||
addr := address.AddrForKey(newKey.pub)
|
addr := address.AddrForKey(newKey.pub)
|
||||||
|
@ -61,11 +72,14 @@ func isBetter(oldPub, newPub ed25519.PublicKey) bool {
|
||||||
|
|
||||||
func doKeys(out chan<- keySet) {
|
func doKeys(out chan<- keySet) {
|
||||||
bestKey := make(ed25519.PublicKey, ed25519.PublicKeySize)
|
bestKey := make(ed25519.PublicKey, ed25519.PublicKeySize)
|
||||||
|
var count uint64
|
||||||
|
count = 0
|
||||||
for idx := range bestKey {
|
for idx := range bestKey {
|
||||||
bestKey[idx] = 0xff
|
bestKey[idx] = 0xff
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
pub, priv, err := ed25519.GenerateKey(nil)
|
pub, priv, err := ed25519.GenerateKey(nil)
|
||||||
|
count++
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -73,6 +87,7 @@ func doKeys(out chan<- keySet) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bestKey = pub
|
bestKey = pub
|
||||||
out <- keySet{priv, pub}
|
out <- keySet{priv, pub, count}
|
||||||
|
count = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
cmd/yggdrasil/chuser_other.go
Normal file
10
cmd/yggdrasil/chuser_other.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris
|
||||||
|
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func chuser(user string) error {
|
||||||
|
return errors.New("setting uid/gid is not supported on this platform")
|
||||||
|
}
|
63
cmd/yggdrasil/chuser_unix.go
Normal file
63
cmd/yggdrasil/chuser_unix.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||||
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func chuser(input string) error {
|
||||||
|
givenUser, givenGroup, _ := strings.Cut(input, ":")
|
||||||
|
if givenUser == "" {
|
||||||
|
return fmt.Errorf("user is empty")
|
||||||
|
}
|
||||||
|
if strings.Contains(input, ":") && givenGroup == "" {
|
||||||
|
return fmt.Errorf("group is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
usr *user.User
|
||||||
|
grp *user.Group
|
||||||
|
uid, gid int
|
||||||
|
)
|
||||||
|
|
||||||
|
if usr, err = user.LookupId(givenUser); err != nil {
|
||||||
|
if usr, err = user.Lookup(givenUser); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if uid, err = strconv.Atoi(usr.Uid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if givenGroup != "" {
|
||||||
|
if grp, err = user.LookupGroupId(givenGroup); err != nil {
|
||||||
|
if grp, err = user.LookupGroup(givenGroup); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gid, _ = strconv.Atoi(grp.Gid)
|
||||||
|
} else {
|
||||||
|
gid, _ = strconv.Atoi(usr.Gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := unix.Setgroups([]int{gid}); err != nil {
|
||||||
|
return fmt.Errorf("setgroups: %d: %v", gid, err)
|
||||||
|
}
|
||||||
|
if err := unix.Setgid(gid); err != nil {
|
||||||
|
return fmt.Errorf("setgid: %d: %v", gid, err)
|
||||||
|
}
|
||||||
|
if err := unix.Setuid(uid); err != nil {
|
||||||
|
return fmt.Errorf("setuid: %d: %v", uid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
80
cmd/yggdrasil/chuser_unix_test.go
Normal file
80
cmd/yggdrasil/chuser_unix_test.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||||
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/user"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Usernames must not contain a number sign.
|
||||||
|
func TestEmptyString(t *testing.T) {
|
||||||
|
if chuser("") == nil {
|
||||||
|
t.Fatal("the empty string is not a valid user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either omit delimiter and group, or omit both.
|
||||||
|
func TestEmptyGroup(t *testing.T) {
|
||||||
|
if chuser("0:") == nil {
|
||||||
|
t.Fatal("the empty group is not allowed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either user only or user and group.
|
||||||
|
func TestGroupOnly(t *testing.T) {
|
||||||
|
if chuser(":0") == nil {
|
||||||
|
t.Fatal("group only is not allowed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usenames must not contain the number sign.
|
||||||
|
func TestInvalidUsername(t *testing.T) {
|
||||||
|
const username = "#user"
|
||||||
|
if chuser(username) == nil {
|
||||||
|
t.Fatalf("'%s' is not a valid username", username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// User IDs must be non-negative.
|
||||||
|
func TestInvalidUserid(t *testing.T) {
|
||||||
|
if chuser("-1") == nil {
|
||||||
|
t.Fatal("User ID cannot be negative")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change to the current user by ID.
|
||||||
|
func TestCurrentUserid(t *testing.T) {
|
||||||
|
usr, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if usr.Uid != "0" {
|
||||||
|
t.Skip("setgroups(2): Only the superuser may set new groups.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = chuser(usr.Uid); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change to a common user by name.
|
||||||
|
func TestCommonUsername(t *testing.T) {
|
||||||
|
usr, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if usr.Uid != "0" {
|
||||||
|
t.Skip("setgroups(2): Only the superuser may set new groups.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := chuser("nobody"); err != nil {
|
||||||
|
if _, ok := err.(user.UnknownUserError); ok {
|
||||||
|
t.Skip(err)
|
||||||
|
}
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,34 +1,29 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/text/encoding/unicode"
|
"suah.dev/protect"
|
||||||
|
|
||||||
"github.com/gologme/log"
|
"github.com/gologme/log"
|
||||||
gsyslog "github.com/hashicorp/go-syslog"
|
gsyslog "github.com/hashicorp/go-syslog"
|
||||||
"github.com/hjson/hjson-go"
|
"github.com/hjson/hjson-go/v4"
|
||||||
"github.com/kardianos/minwinsvc"
|
"github.com/kardianos/minwinsvc"
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc"
|
"github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||||
|
@ -44,75 +39,302 @@ type node struct {
|
||||||
admin *admin.AdminSocket
|
admin *admin.AdminSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
func readConfig(log *log.Logger, useconf bool, useconffile string, normaliseconf bool) *config.NodeConfig {
|
// The main function is responsible for configuring and starting Yggdrasil.
|
||||||
// Use a configuration file. If -useconf, the configuration will be read
|
func main() {
|
||||||
// from stdin. If -useconffile, the configuration will be read from the
|
// Not all operations are coverable with pledge(2), so immediately
|
||||||
// filesystem.
|
// limit file system access with unveil(2), effectively preventing
|
||||||
var conf []byte
|
// "proc exec" promises right from the start:
|
||||||
var err error
|
//
|
||||||
if useconffile != "" {
|
// - read arbitrary config file
|
||||||
// Read the file from the filesystem
|
// - create/write arbitrary log file
|
||||||
conf, err = os.ReadFile(useconffile)
|
// - read/write/chmod/remove admin socket, if at all
|
||||||
|
if err := protect.Unveil("/", "rwc"); err != nil {
|
||||||
|
panic(fmt.Sprintf("unveil: / rwc: %v", err))
|
||||||
|
}
|
||||||
|
if err := protect.UnveilBlock(); err != nil {
|
||||||
|
panic(fmt.Sprintf("unveil: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
genconf := flag.Bool("genconf", false, "print a new config to stdout")
|
||||||
|
useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin")
|
||||||
|
useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path")
|
||||||
|
normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised")
|
||||||
|
exportkey := flag.Bool("exportkey", false, "use in combination with either -useconf or -useconffile, outputs your private key in PEM format")
|
||||||
|
confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON")
|
||||||
|
autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)")
|
||||||
|
ver := flag.Bool("version", false, "prints the version of this build")
|
||||||
|
logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"")
|
||||||
|
getaddr := flag.Bool("address", false, "use in combination with either -useconf or -useconffile, outputs your IPv6 address")
|
||||||
|
getsnet := flag.Bool("subnet", false, "use in combination with either -useconf or -useconffile, outputs your IPv6 subnet")
|
||||||
|
getpkey := flag.Bool("publickey", false, "use in combination with either -useconf or -useconffile, outputs your public key")
|
||||||
|
loglevel := flag.String("loglevel", "info", "loglevel to enable")
|
||||||
|
chuserto := flag.String("user", "", "user (and, optionally, group) to set UID/GID to")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
// Catch interrupts from the operating system to exit gracefully.
|
||||||
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
|
// Create a new logger that logs output to stdout.
|
||||||
|
var logger *log.Logger
|
||||||
|
switch *logto {
|
||||||
|
case "stdout":
|
||||||
|
logger = log.New(os.Stdout, "", log.Flags())
|
||||||
|
|
||||||
|
case "syslog":
|
||||||
|
if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", version.BuildName()); err == nil {
|
||||||
|
logger = log.New(syslogger, "", log.Flags()&^(log.Ldate|log.Ltime))
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil {
|
||||||
|
logger = log.New(logfd, "", log.Flags())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if logger == nil {
|
||||||
|
logger = log.New(os.Stdout, "", log.Flags())
|
||||||
|
logger.Warnln("Logging defaulting to stdout")
|
||||||
|
}
|
||||||
|
if *normaliseconf {
|
||||||
|
setLogLevel("error", logger)
|
||||||
} else {
|
} else {
|
||||||
// Read the file from stdin.
|
setLogLevel(*loglevel, logger)
|
||||||
conf, err = io.ReadAll(os.Stdin)
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
cfg := config.GenerateConfig()
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case *ver:
|
||||||
|
fmt.Println("Build name:", version.BuildName())
|
||||||
|
fmt.Println("Build version:", version.BuildVersion())
|
||||||
|
return
|
||||||
|
|
||||||
|
case *autoconf:
|
||||||
|
// Use an autoconf-generated config, this will give us random keys and
|
||||||
|
// port numbers, and will use an automatically selected TUN interface.
|
||||||
|
|
||||||
|
case *useconf:
|
||||||
|
if _, err := cfg.ReadFrom(os.Stdin); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *useconffile != "":
|
||||||
|
f, err := os.Open(*useconffile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if _, err := cfg.ReadFrom(f); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_ = f.Close()
|
||||||
|
|
||||||
|
case *genconf:
|
||||||
|
cfg.AdminListen = ""
|
||||||
|
var bs []byte
|
||||||
|
if *confjson {
|
||||||
|
bs, err = json.MarshalIndent(cfg, "", " ")
|
||||||
|
} else {
|
||||||
|
bs, err = hjson.Marshal(cfg)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(bs))
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
fmt.Println("Usage:")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
|
||||||
|
if *getaddr || *getsnet {
|
||||||
|
fmt.Println("\nError: You need to specify some config data using -useconf or -useconffile.")
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
// If there's a byte order mark - which Windows 10 is now incredibly fond of
|
|
||||||
// throwing everywhere when it's converting things into UTF-16 for the hell
|
privateKey := ed25519.PrivateKey(cfg.PrivateKey)
|
||||||
// of it - remove it and decode back down into UTF-8. This is necessary
|
publicKey := privateKey.Public().(ed25519.PublicKey)
|
||||||
// because hjson doesn't know what to do with UTF-16 and will panic
|
|
||||||
if bytes.Equal(conf[0:2], []byte{0xFF, 0xFE}) ||
|
switch {
|
||||||
bytes.Equal(conf[0:2], []byte{0xFE, 0xFF}) {
|
case *getaddr:
|
||||||
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
|
addr := address.AddrForKey(publicKey)
|
||||||
decoder := utf.NewDecoder()
|
ip := net.IP(addr[:])
|
||||||
conf, err = decoder.Bytes(conf)
|
fmt.Println(ip.String())
|
||||||
|
return
|
||||||
|
|
||||||
|
case *getsnet:
|
||||||
|
snet := address.SubnetForKey(publicKey)
|
||||||
|
ipnet := net.IPNet{
|
||||||
|
IP: append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
Mask: net.CIDRMask(len(snet)*8, 128),
|
||||||
|
}
|
||||||
|
fmt.Println(ipnet.String())
|
||||||
|
return
|
||||||
|
|
||||||
|
case *getpkey:
|
||||||
|
fmt.Println(hex.EncodeToString(publicKey))
|
||||||
|
return
|
||||||
|
|
||||||
|
case *normaliseconf:
|
||||||
|
cfg.AdminListen = ""
|
||||||
|
if cfg.PrivateKeyPath != "" {
|
||||||
|
cfg.PrivateKey = nil
|
||||||
|
}
|
||||||
|
var bs []byte
|
||||||
|
if *confjson {
|
||||||
|
bs, err = json.MarshalIndent(cfg, "", " ")
|
||||||
|
} else {
|
||||||
|
bs, err = hjson.Marshal(cfg)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(bs))
|
||||||
|
return
|
||||||
|
|
||||||
|
case *exportkey:
|
||||||
|
pem, err := cfg.MarshalPEMPrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(pem))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n := &node{}
|
||||||
|
|
||||||
|
// Set up the Yggdrasil node itself.
|
||||||
|
{
|
||||||
|
iprange := net.IPNet{
|
||||||
|
IP: net.ParseIP("200::"),
|
||||||
|
Mask: net.CIDRMask(7, 128),
|
||||||
|
}
|
||||||
|
options := []core.SetupOption{
|
||||||
|
core.NodeInfo(cfg.NodeInfo),
|
||||||
|
core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
|
||||||
|
core.PeerFilter(func(ip net.IP) bool {
|
||||||
|
return !iprange.Contains(ip)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
for _, addr := range cfg.Listen {
|
||||||
|
options = append(options, core.ListenAddress(addr))
|
||||||
|
}
|
||||||
|
for _, peer := range cfg.Peers {
|
||||||
|
options = append(options, core.Peer{URI: peer})
|
||||||
|
}
|
||||||
|
for intf, peers := range cfg.InterfacePeers {
|
||||||
|
for _, peer := range peers {
|
||||||
|
options = append(options, core.Peer{URI: peer, SourceInterface: intf})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, allowed := range cfg.AllowedPublicKeys {
|
||||||
|
k, err := hex.DecodeString(allowed)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
options = append(options, core.AllowedPublicKey(k[:]))
|
||||||
|
}
|
||||||
|
if n.core, err = core.New(cfg.Certificate, logger, options...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
address, subnet := n.core.Address(), n.core.Subnet()
|
||||||
|
logger.Printf("Your public key is %s", hex.EncodeToString(n.core.PublicKey()))
|
||||||
|
logger.Printf("Your IPv6 address is %s", address.String())
|
||||||
|
logger.Printf("Your IPv6 subnet is %s", subnet.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the admin socket.
|
||||||
|
{
|
||||||
|
options := []admin.SetupOption{
|
||||||
|
admin.ListenAddress(cfg.AdminListen),
|
||||||
|
}
|
||||||
|
if cfg.LogLookups {
|
||||||
|
options = append(options, admin.LogLookups{})
|
||||||
|
}
|
||||||
|
if n.admin, err = admin.New(n.core, logger, options...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if n.admin != nil {
|
||||||
|
n.admin.SetupAdminHandlers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the multicast module.
|
||||||
|
{
|
||||||
|
options := []multicast.SetupOption{}
|
||||||
|
for _, intf := range cfg.MulticastInterfaces {
|
||||||
|
options = append(options, multicast.MulticastInterface{
|
||||||
|
Regex: regexp.MustCompile(intf.Regex),
|
||||||
|
Beacon: intf.Beacon,
|
||||||
|
Listen: intf.Listen,
|
||||||
|
Port: intf.Port,
|
||||||
|
Priority: uint8(intf.Priority),
|
||||||
|
Password: intf.Password,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if n.multicast, err = multicast.New(n.core, logger, options...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if n.admin != nil && n.multicast != nil {
|
||||||
|
n.multicast.SetupAdminHandlers(n.admin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the TUN module.
|
||||||
|
{
|
||||||
|
options := []tun.SetupOption{
|
||||||
|
tun.InterfaceName(cfg.IfName),
|
||||||
|
tun.InterfaceMTU(cfg.IfMTU),
|
||||||
|
}
|
||||||
|
if n.tun, err = tun.New(ipv6rwc.NewReadWriteCloser(n.core), logger, options...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if n.admin != nil && n.tun != nil {
|
||||||
|
n.tun.SetupAdminHandlers(n.admin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Windows service shutdown
|
||||||
|
minwinsvc.SetOnExit(func() {
|
||||||
|
logger.Infof("Shutting down service ...")
|
||||||
|
cancel()
|
||||||
|
// Wait for all parts to shutdown properly
|
||||||
|
<-done
|
||||||
|
})
|
||||||
|
|
||||||
|
// Change user if requested
|
||||||
|
if *chuserto != "" {
|
||||||
|
err = chuser(*chuserto)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Generate a new configuration - this gives us a set of sane defaults -
|
|
||||||
// then parse the configuration we loaded above on top of it. The effect
|
|
||||||
// of this is that any configuration item that is missing from the provided
|
|
||||||
// configuration will use a sane default.
|
|
||||||
cfg := defaults.GenerateConfig()
|
|
||||||
var dat map[string]interface{}
|
|
||||||
if err := hjson.Unmarshal(conf, &dat); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// Sanitise the config
|
|
||||||
confJson, err := json.Marshal(dat)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(confJson, &cfg); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// Overlay our newly mapped configuration onto the autoconf node config that
|
|
||||||
// we generated above.
|
|
||||||
if err = mapstructure.Decode(dat, &cfg); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generates a new configuration and returns it in HJSON format. This is used
|
// Promise final modes of operation. At this point, if at all:
|
||||||
// with -genconf.
|
// - raw socket is created/open
|
||||||
func doGenconf(isjson bool) string {
|
// - admin socket is created/open
|
||||||
cfg := defaults.GenerateConfig()
|
// - privileges are dropped to non-root user
|
||||||
var bs []byte
|
//
|
||||||
var err error
|
// Peers, InterfacePeers, Listen can be UNIX sockets;
|
||||||
if isjson {
|
// Go's net.Listen.Close() deletes files on shutdown.
|
||||||
bs, err = json.MarshalIndent(cfg, "", " ")
|
promises := []string{"stdio", "cpath", "inet", "unix", "dns"}
|
||||||
} else {
|
if len(cfg.MulticastInterfaces) > 0 {
|
||||||
bs, err = hjson.Marshal(cfg)
|
promises = append(promises, "mcast")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err := protect.Pledge(strings.Join(promises, " ")); err != nil {
|
||||||
panic(err)
|
panic(fmt.Sprintf("pledge: %v: %v", promises, err))
|
||||||
}
|
}
|
||||||
return string(bs)
|
|
||||||
|
// Block until we are told to shut down.
|
||||||
|
<-ctx.Done()
|
||||||
|
|
||||||
|
// Shut down the node.
|
||||||
|
_ = n.admin.Stop()
|
||||||
|
_ = n.multicast.Stop()
|
||||||
|
_ = n.tun.Stop()
|
||||||
|
n.core.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setLogLevel(loglevel string, logger *log.Logger) {
|
func setLogLevel(loglevel string, logger *log.Logger) {
|
||||||
|
@ -140,269 +362,3 @@ func setLogLevel(loglevel string, logger *log.Logger) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type yggArgs struct {
|
|
||||||
genconf bool
|
|
||||||
useconf bool
|
|
||||||
normaliseconf bool
|
|
||||||
confjson bool
|
|
||||||
autoconf bool
|
|
||||||
ver bool
|
|
||||||
getaddr bool
|
|
||||||
getsnet bool
|
|
||||||
useconffile string
|
|
||||||
logto string
|
|
||||||
loglevel string
|
|
||||||
}
|
|
||||||
|
|
||||||
func getArgs() yggArgs {
|
|
||||||
genconf := flag.Bool("genconf", false, "print a new config to stdout")
|
|
||||||
useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin")
|
|
||||||
useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path")
|
|
||||||
normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised")
|
|
||||||
confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON")
|
|
||||||
autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)")
|
|
||||||
ver := flag.Bool("version", false, "prints the version of this build")
|
|
||||||
logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"")
|
|
||||||
getaddr := flag.Bool("address", false, "returns the IPv6 address as derived from the supplied configuration")
|
|
||||||
getsnet := flag.Bool("subnet", false, "returns the IPv6 subnet as derived from the supplied configuration")
|
|
||||||
loglevel := flag.String("loglevel", "info", "loglevel to enable")
|
|
||||||
flag.Parse()
|
|
||||||
return yggArgs{
|
|
||||||
genconf: *genconf,
|
|
||||||
useconf: *useconf,
|
|
||||||
useconffile: *useconffile,
|
|
||||||
normaliseconf: *normaliseconf,
|
|
||||||
confjson: *confjson,
|
|
||||||
autoconf: *autoconf,
|
|
||||||
ver: *ver,
|
|
||||||
logto: *logto,
|
|
||||||
getaddr: *getaddr,
|
|
||||||
getsnet: *getsnet,
|
|
||||||
loglevel: *loglevel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The main function is responsible for configuring and starting Yggdrasil.
|
|
||||||
func run(args yggArgs, ctx context.Context) {
|
|
||||||
// Create a new logger that logs output to stdout.
|
|
||||||
var logger *log.Logger
|
|
||||||
switch args.logto {
|
|
||||||
case "stdout":
|
|
||||||
logger = log.New(os.Stdout, "", log.Flags())
|
|
||||||
case "syslog":
|
|
||||||
if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", version.BuildName()); err == nil {
|
|
||||||
logger = log.New(syslogger, "", log.Flags())
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if logfd, err := os.OpenFile(args.logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil {
|
|
||||||
logger = log.New(logfd, "", log.Flags())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if logger == nil {
|
|
||||||
logger = log.New(os.Stdout, "", log.Flags())
|
|
||||||
logger.Warnln("Logging defaulting to stdout")
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.normaliseconf {
|
|
||||||
setLogLevel("error", logger)
|
|
||||||
} else {
|
|
||||||
setLogLevel(args.loglevel, logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
var cfg *config.NodeConfig
|
|
||||||
var err error
|
|
||||||
switch {
|
|
||||||
case args.ver:
|
|
||||||
fmt.Println("Build name:", version.BuildName())
|
|
||||||
fmt.Println("Build version:", version.BuildVersion())
|
|
||||||
return
|
|
||||||
case args.autoconf:
|
|
||||||
// Use an autoconf-generated config, this will give us random keys and
|
|
||||||
// port numbers, and will use an automatically selected TUN interface.
|
|
||||||
cfg = defaults.GenerateConfig()
|
|
||||||
case args.useconffile != "" || args.useconf:
|
|
||||||
// Read the configuration from either stdin or from the filesystem
|
|
||||||
cfg = readConfig(logger, args.useconf, args.useconffile, args.normaliseconf)
|
|
||||||
// If the -normaliseconf option was specified then remarshal the above
|
|
||||||
// configuration and print it back to stdout. This lets the user update
|
|
||||||
// their configuration file with newly mapped names (like above) or to
|
|
||||||
// convert from plain JSON to commented HJSON.
|
|
||||||
if args.normaliseconf {
|
|
||||||
var bs []byte
|
|
||||||
if args.confjson {
|
|
||||||
bs, err = json.MarshalIndent(cfg, "", " ")
|
|
||||||
} else {
|
|
||||||
bs, err = hjson.Marshal(cfg)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(bs))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case args.genconf:
|
|
||||||
// Generate a new configuration and print it to stdout.
|
|
||||||
fmt.Println(doGenconf(args.confjson))
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
// No flags were provided, therefore print the list of flags to stdout.
|
|
||||||
fmt.Println("Usage:")
|
|
||||||
flag.PrintDefaults()
|
|
||||||
|
|
||||||
if args.getaddr || args.getsnet {
|
|
||||||
fmt.Println("\nError: You need to specify some config data using -useconf or -useconffile.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Have we got a working configuration? If we don't then it probably means
|
|
||||||
// that neither -autoconf, -useconf or -useconffile were set above. Stop
|
|
||||||
// if we don't.
|
|
||||||
if cfg == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Have we been asked for the node address yet? If so, print it and then stop.
|
|
||||||
getNodeKey := func() ed25519.PublicKey {
|
|
||||||
if pubkey, err := hex.DecodeString(cfg.PrivateKey); err == nil {
|
|
||||||
return ed25519.PrivateKey(pubkey).Public().(ed25519.PublicKey)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case args.getaddr:
|
|
||||||
if key := getNodeKey(); key != nil {
|
|
||||||
addr := address.AddrForKey(key)
|
|
||||||
ip := net.IP(addr[:])
|
|
||||||
fmt.Println(ip.String())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case args.getsnet:
|
|
||||||
if key := getNodeKey(); key != nil {
|
|
||||||
snet := address.SubnetForKey(key)
|
|
||||||
ipnet := net.IPNet{
|
|
||||||
IP: append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0),
|
|
||||||
Mask: net.CIDRMask(len(snet)*8, 128),
|
|
||||||
}
|
|
||||||
fmt.Println(ipnet.String())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n := &node{}
|
|
||||||
|
|
||||||
// Setup the Yggdrasil node itself.
|
|
||||||
{
|
|
||||||
sk, err := hex.DecodeString(cfg.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
options := []core.SetupOption{
|
|
||||||
core.NodeInfo(cfg.NodeInfo),
|
|
||||||
core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
|
|
||||||
}
|
|
||||||
for _, addr := range cfg.Listen {
|
|
||||||
options = append(options, core.ListenAddress(addr))
|
|
||||||
}
|
|
||||||
for _, peer := range cfg.Peers {
|
|
||||||
options = append(options, core.Peer{URI: peer})
|
|
||||||
}
|
|
||||||
for intf, peers := range cfg.InterfacePeers {
|
|
||||||
for _, peer := range peers {
|
|
||||||
options = append(options, core.Peer{URI: peer, SourceInterface: intf})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, allowed := range cfg.AllowedPublicKeys {
|
|
||||||
k, err := hex.DecodeString(allowed)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
options = append(options, core.AllowedPublicKey(k[:]))
|
|
||||||
}
|
|
||||||
if n.core, err = core.New(sk[:], logger, options...); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the admin socket.
|
|
||||||
{
|
|
||||||
options := []admin.SetupOption{
|
|
||||||
admin.ListenAddress(cfg.AdminListen),
|
|
||||||
}
|
|
||||||
if n.admin, err = admin.New(n.core, logger, options...); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if n.admin != nil {
|
|
||||||
n.admin.SetupAdminHandlers()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the multicast module.
|
|
||||||
{
|
|
||||||
options := []multicast.SetupOption{}
|
|
||||||
for _, intf := range cfg.MulticastInterfaces {
|
|
||||||
options = append(options, multicast.MulticastInterface{
|
|
||||||
Regex: regexp.MustCompile(intf.Regex),
|
|
||||||
Beacon: intf.Beacon,
|
|
||||||
Listen: intf.Listen,
|
|
||||||
Port: intf.Port,
|
|
||||||
Priority: uint8(intf.Priority),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if n.multicast, err = multicast.New(n.core, logger, options...); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if n.admin != nil && n.multicast != nil {
|
|
||||||
n.multicast.SetupAdminHandlers(n.admin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the TUN module.
|
|
||||||
{
|
|
||||||
options := []tun.SetupOption{
|
|
||||||
tun.InterfaceName(cfg.IfName),
|
|
||||||
tun.InterfaceMTU(cfg.IfMTU),
|
|
||||||
}
|
|
||||||
if n.tun, err = tun.New(ipv6rwc.NewReadWriteCloser(n.core), logger, options...); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if n.admin != nil && n.tun != nil {
|
|
||||||
n.tun.SetupAdminHandlers(n.admin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make some nice output that tells us what our IPv6 address and subnet are.
|
|
||||||
// This is just logged to stdout for the user.
|
|
||||||
address := n.core.Address()
|
|
||||||
subnet := n.core.Subnet()
|
|
||||||
public := n.core.GetSelf().Key
|
|
||||||
logger.Infof("Your public key is %s", hex.EncodeToString(public[:]))
|
|
||||||
logger.Infof("Your IPv6 address is %s", address.String())
|
|
||||||
logger.Infof("Your IPv6 subnet is %s", subnet.String())
|
|
||||||
|
|
||||||
// Block until we are told to shut down.
|
|
||||||
<-ctx.Done()
|
|
||||||
|
|
||||||
// Shut down the node.
|
|
||||||
_ = n.admin.Stop()
|
|
||||||
_ = n.multicast.Stop()
|
|
||||||
_ = n.tun.Stop()
|
|
||||||
n.core.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
args := getArgs()
|
|
||||||
|
|
||||||
// Catch interrupts from the operating system to exit gracefully.
|
|
||||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
||||||
|
|
||||||
// Capture the service being stopped on Windows.
|
|
||||||
minwinsvc.SetOnExit(cancel)
|
|
||||||
|
|
||||||
// Start the node, block and then wait for it to shut down.
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
run(args, ctx)
|
|
||||||
}()
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,10 +7,10 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/hjson/hjson-go"
|
"github.com/hjson/hjson-go/v4"
|
||||||
"golang.org/x/text/encoding/unicode"
|
"golang.org/x/text/encoding/unicode"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CmdLineEnv struct {
|
type CmdLineEnv struct {
|
||||||
|
@ -21,7 +21,7 @@ type CmdLineEnv struct {
|
||||||
|
|
||||||
func newCmdLineEnv() CmdLineEnv {
|
func newCmdLineEnv() CmdLineEnv {
|
||||||
var cmdLineEnv CmdLineEnv
|
var cmdLineEnv CmdLineEnv
|
||||||
cmdLineEnv.endpoint = defaults.GetDefaults().DefaultAdminListen
|
cmdLineEnv.endpoint = config.GetDefaults().DefaultAdminListen
|
||||||
return cmdLineEnv
|
return cmdLineEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,10 +38,8 @@ func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() {
|
||||||
fmt.Println("Examples:")
|
fmt.Println("Examples:")
|
||||||
fmt.Println(" - ", os.Args[0], "list")
|
fmt.Println(" - ", os.Args[0], "list")
|
||||||
fmt.Println(" - ", os.Args[0], "getPeers")
|
fmt.Println(" - ", os.Args[0], "getPeers")
|
||||||
fmt.Println(" - ", os.Args[0], "-v getSelf")
|
fmt.Println(" - ", os.Args[0], "-endpoint=tcp://localhost:9001 getPeers")
|
||||||
fmt.Println(" - ", os.Args[0], "setTunTap name=auto mtu=1500 tap_mode=false")
|
fmt.Println(" - ", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getPeers")
|
||||||
fmt.Println(" - ", os.Args[0], "-endpoint=tcp://localhost:9001 getDHT")
|
|
||||||
fmt.Println(" - ", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getDHT")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
server := flag.String("endpoint", cmdLineEnv.endpoint, "Admin socket endpoint")
|
server := flag.String("endpoint", cmdLineEnv.endpoint, "Admin socket endpoint")
|
||||||
|
@ -58,31 +56,31 @@ func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() {
|
||||||
|
|
||||||
func (cmdLineEnv *CmdLineEnv) setEndpoint(logger *log.Logger) {
|
func (cmdLineEnv *CmdLineEnv) setEndpoint(logger *log.Logger) {
|
||||||
if cmdLineEnv.server == cmdLineEnv.endpoint {
|
if cmdLineEnv.server == cmdLineEnv.endpoint {
|
||||||
if config, err := os.ReadFile(defaults.GetDefaults().DefaultConfigFile); err == nil {
|
if cfg, err := os.ReadFile(config.GetDefaults().DefaultConfigFile); err == nil {
|
||||||
if bytes.Equal(config[0:2], []byte{0xFF, 0xFE}) ||
|
if bytes.Equal(cfg[0:2], []byte{0xFF, 0xFE}) ||
|
||||||
bytes.Equal(config[0:2], []byte{0xFE, 0xFF}) {
|
bytes.Equal(cfg[0:2], []byte{0xFE, 0xFF}) {
|
||||||
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
|
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
|
||||||
decoder := utf.NewDecoder()
|
decoder := utf.NewDecoder()
|
||||||
config, err = decoder.Bytes(config)
|
cfg, err = decoder.Bytes(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var dat map[string]interface{}
|
var dat map[string]interface{}
|
||||||
if err := hjson.Unmarshal(config, &dat); err != nil {
|
if err := hjson.Unmarshal(cfg, &dat); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if ep, ok := dat["AdminListen"].(string); ok && (ep != "none" && ep != "") {
|
if ep, ok := dat["AdminListen"].(string); ok && (ep != "none" && ep != "") {
|
||||||
cmdLineEnv.endpoint = ep
|
cmdLineEnv.endpoint = ep
|
||||||
logger.Println("Found platform default config file", defaults.GetDefaults().DefaultConfigFile)
|
logger.Println("Found platform default config file", config.GetDefaults().DefaultConfigFile)
|
||||||
logger.Println("Using endpoint", cmdLineEnv.endpoint, "from AdminListen")
|
logger.Println("Using endpoint", cmdLineEnv.endpoint, "from AdminListen")
|
||||||
} else {
|
} else {
|
||||||
logger.Println("Configuration file doesn't contain appropriate AdminListen option")
|
logger.Println("Configuration file doesn't contain appropriate AdminListen option")
|
||||||
logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen)
|
logger.Println("Falling back to platform default", config.GetDefaults().DefaultAdminListen)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.Println("Can't open config file from default location", defaults.GetDefaults().DefaultConfigFile)
|
logger.Println("Can't open config file from default location", config.GetDefaults().DefaultConfigFile)
|
||||||
logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen)
|
logger.Println("Falling back to platform default", config.GetDefaults().DefaultAdminListen)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cmdLineEnv.endpoint = cmdLineEnv.server
|
cmdLineEnv.endpoint = cmdLineEnv.server
|
||||||
|
|
|
@ -13,6 +13,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"suah.dev/protect"
|
||||||
|
|
||||||
"github.com/olekukonko/tablewriter"
|
"github.com/olekukonko/tablewriter"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||||
|
@ -22,6 +24,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// read config, speak DNS/TCP and/or over a UNIX socket
|
||||||
|
if err := protect.Pledge("stdio rpath inet unix dns"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
// makes sure we can use defer and still return an error code to the OS
|
// makes sure we can use defer and still return an error code to the OS
|
||||||
os.Exit(run())
|
os.Exit(run())
|
||||||
}
|
}
|
||||||
|
@ -78,6 +85,11 @@ func run() int {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// config and socket are done, work without unprivileges
|
||||||
|
if err := protect.Pledge("stdio"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
logger.Println("Connected")
|
logger.Println("Connected")
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
|
@ -165,7 +177,7 @@ func run() int {
|
||||||
table.Append([]string{"Build version:", resp.BuildVersion})
|
table.Append([]string{"Build version:", resp.BuildVersion})
|
||||||
table.Append([]string{"IPv6 address:", resp.IPAddress})
|
table.Append([]string{"IPv6 address:", resp.IPAddress})
|
||||||
table.Append([]string{"IPv6 subnet:", resp.Subnet})
|
table.Append([]string{"IPv6 subnet:", resp.Subnet})
|
||||||
table.Append([]string{"Coordinates:", fmt.Sprintf("%v", resp.Coords)})
|
table.Append([]string{"Routing table size:", fmt.Sprintf("%d", resp.RoutingEntries)})
|
||||||
table.Append([]string{"Public key:", resp.PublicKey})
|
table.Append([]string{"Public key:", resp.PublicKey})
|
||||||
table.Render()
|
table.Render()
|
||||||
|
|
||||||
|
@ -174,33 +186,61 @@ func run() int {
|
||||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
table.SetHeader([]string{"Port", "Public Key", "IP Address", "Uptime", "RX", "TX", "Pr", "URI"})
|
table.SetHeader([]string{"URI", "State", "Dir", "IP Address", "Uptime", "RTT", "RX", "TX", "Down", "Up", "Pr", "Cost", "Last Error"})
|
||||||
for _, peer := range resp.Peers {
|
for _, peer := range resp.Peers {
|
||||||
|
state, lasterr, dir, rtt, rxr, txr := "Up", "-", "Out", "-", "-", "-"
|
||||||
|
if !peer.Up {
|
||||||
|
state, lasterr = "Down", fmt.Sprintf("%s ago: %s", peer.LastErrorTime.Round(time.Second), peer.LastError)
|
||||||
|
} else if rttms := float64(peer.Latency.Microseconds()) / 1000; rttms > 0 {
|
||||||
|
rtt = fmt.Sprintf("%.02fms", rttms)
|
||||||
|
}
|
||||||
|
if peer.Inbound {
|
||||||
|
dir = "In"
|
||||||
|
}
|
||||||
|
uristring := peer.URI
|
||||||
|
if uri, err := url.Parse(peer.URI); err == nil {
|
||||||
|
uri.RawQuery = ""
|
||||||
|
uristring = uri.String()
|
||||||
|
}
|
||||||
|
if peer.RXRate > 0 {
|
||||||
|
rxr = peer.RXRate.String() + "/s"
|
||||||
|
}
|
||||||
|
if peer.TXRate > 0 {
|
||||||
|
txr = peer.TXRate.String() + "/s"
|
||||||
|
}
|
||||||
table.Append([]string{
|
table.Append([]string{
|
||||||
fmt.Sprintf("%d", peer.Port),
|
uristring,
|
||||||
peer.PublicKey,
|
state,
|
||||||
|
dir,
|
||||||
peer.IPAddress,
|
peer.IPAddress,
|
||||||
(time.Duration(peer.Uptime) * time.Second).String(),
|
(time.Duration(peer.Uptime) * time.Second).String(),
|
||||||
|
rtt,
|
||||||
peer.RXBytes.String(),
|
peer.RXBytes.String(),
|
||||||
peer.TXBytes.String(),
|
peer.TXBytes.String(),
|
||||||
|
rxr,
|
||||||
|
txr,
|
||||||
fmt.Sprintf("%d", peer.Priority),
|
fmt.Sprintf("%d", peer.Priority),
|
||||||
peer.Remote,
|
fmt.Sprintf("%d", peer.Cost),
|
||||||
|
lasterr,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
table.Render()
|
table.Render()
|
||||||
|
|
||||||
case "getdht":
|
case "gettree":
|
||||||
var resp admin.GetDHTResponse
|
var resp admin.GetTreeResponse
|
||||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
table.SetHeader([]string{"Public Key", "IP Address", "Port", "Rest"})
|
//table.SetHeader([]string{"Public Key", "IP Address", "Port", "Rest"})
|
||||||
for _, dht := range resp.DHT {
|
table.SetHeader([]string{"Public Key", "IP Address", "Parent", "Sequence"})
|
||||||
|
for _, tree := range resp.Tree {
|
||||||
table.Append([]string{
|
table.Append([]string{
|
||||||
dht.PublicKey,
|
tree.PublicKey,
|
||||||
dht.IPAddress,
|
tree.IPAddress,
|
||||||
fmt.Sprintf("%d", dht.Port),
|
tree.Parent,
|
||||||
fmt.Sprintf("%d", dht.Rest),
|
fmt.Sprintf("%d", tree.Sequence),
|
||||||
|
//fmt.Sprintf("%d", dht.Port),
|
||||||
|
//fmt.Sprintf("%d", dht.Rest),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
table.Render()
|
table.Render()
|
||||||
|
@ -210,12 +250,13 @@ func run() int {
|
||||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
table.SetHeader([]string{"Public Key", "IP Address", "Path"})
|
table.SetHeader([]string{"Public Key", "IP Address", "Path", "Seq"})
|
||||||
for _, p := range resp.Paths {
|
for _, p := range resp.Paths {
|
||||||
table.Append([]string{
|
table.Append([]string{
|
||||||
p.PublicKey,
|
p.PublicKey,
|
||||||
p.IPAddress,
|
p.IPAddress,
|
||||||
fmt.Sprintf("%v", p.Path),
|
fmt.Sprintf("%v", p.Path),
|
||||||
|
fmt.Sprintf("%d", p.Sequence),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
table.Render()
|
table.Render()
|
||||||
|
@ -252,9 +293,21 @@ func run() int {
|
||||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
table.SetHeader([]string{"Interface"})
|
fmtBool := func(b bool) string {
|
||||||
|
if b {
|
||||||
|
return "Yes"
|
||||||
|
}
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
table.SetHeader([]string{"Name", "Listen Address", "Beacon", "Listen", "Password"})
|
||||||
for _, p := range resp.Interfaces {
|
for _, p := range resp.Interfaces {
|
||||||
table.Append([]string{p})
|
table.Append([]string{
|
||||||
|
p.Name,
|
||||||
|
p.Address,
|
||||||
|
fmtBool(p.Beacon),
|
||||||
|
fmtBool(p.Listen),
|
||||||
|
fmtBool(p.Password),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
table.Render()
|
table.Render()
|
||||||
|
|
||||||
|
|
BIN
contrib/.DS_Store
vendored
Normal file
BIN
contrib/.DS_Store
vendored
Normal file
Binary file not shown.
|
@ -1,7 +1,5 @@
|
||||||
/*
|
/*
|
||||||
|
|
||||||
This file generates crypto keys for [ansible-yggdrasil](https://github.com/jcgruenhage/ansible-yggdrasil/)
|
This file generates crypto keys for [ansible-yggdrasil](https://github.com/jcgruenhage/ansible-yggdrasil/)
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
11
contrib/apparmor/usr.bin.yggdrasilctl
Normal file
11
contrib/apparmor/usr.bin.yggdrasilctl
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# Last Modified: Mon Feb 3 22:19:45 2025
|
||||||
|
include <tunables/global>
|
||||||
|
|
||||||
|
/usr/bin/yggdrasilctl {
|
||||||
|
include <abstractions/base>
|
||||||
|
|
||||||
|
/etc/yggdrasil.conf rw,
|
||||||
|
/run/yggdrasil.sock rw,
|
||||||
|
owner /sys/kernel/mm/transparent_hugepage/hpage_pmd_size r,
|
||||||
|
|
||||||
|
}
|
|
@ -21,13 +21,16 @@ if [ $PKGBRANCH = "master" ]; then
|
||||||
PKGREPLACES=yggdrasil-develop
|
PKGREPLACES=yggdrasil-develop
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $PKGARCH = "amd64" ]; then GOARCH=amd64 GOOS=linux ./build
|
GOLDFLAGS="-X github.com/yggdrasil-network/yggdrasil-go/src/config.defaultConfig=/etc/yggdrasil/yggdrasil.conf"
|
||||||
elif [ $PKGARCH = "i386" ]; then GOARCH=386 GOOS=linux ./build
|
GOLDFLAGS="${GOLDFLAGS} -X github.com/yggdrasil-network/yggdrasil-go/src/config.defaultAdminListen=unix:///var/run/yggdrasil/yggdrasil.sock"
|
||||||
elif [ $PKGARCH = "mipsel" ]; then GOARCH=mipsle GOOS=linux ./build
|
|
||||||
elif [ $PKGARCH = "mips" ]; then GOARCH=mips64 GOOS=linux ./build
|
if [ $PKGARCH = "amd64" ]; then GOARCH=amd64 GOOS=linux ./build -l "${GOLDFLAGS}"
|
||||||
elif [ $PKGARCH = "armhf" ]; then GOARCH=arm GOOS=linux GOARM=6 ./build
|
elif [ $PKGARCH = "i386" ]; then GOARCH=386 GOOS=linux ./build -l "${GOLDFLAGS}"
|
||||||
elif [ $PKGARCH = "arm64" ]; then GOARCH=arm64 GOOS=linux ./build
|
elif [ $PKGARCH = "mipsel" ]; then GOARCH=mipsle GOOS=linux ./build -l "${GOLDFLAGS}"
|
||||||
elif [ $PKGARCH = "armel" ]; then GOARCH=arm GOOS=linux GOARM=5 ./build
|
elif [ $PKGARCH = "mips" ]; then GOARCH=mips64 GOOS=linux ./build -l "${GOLDFLAGS}"
|
||||||
|
elif [ $PKGARCH = "armhf" ]; then GOARCH=arm GOOS=linux GOARM=6 ./build -l "${GOLDFLAGS}"
|
||||||
|
elif [ $PKGARCH = "arm64" ]; then GOARCH=arm64 GOOS=linux ./build -l "${GOLDFLAGS}"
|
||||||
|
elif [ $PKGARCH = "armel" ]; then GOARCH=arm GOOS=linux GOARM=5 ./build -l "${GOLDFLAGS}"
|
||||||
else
|
else
|
||||||
echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf,arm64,armel"
|
echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf,arm64,armel"
|
||||||
exit 1
|
exit 1
|
||||||
|
@ -38,7 +41,7 @@ echo "Building $PKGFILE"
|
||||||
mkdir -p /tmp/$PKGNAME/
|
mkdir -p /tmp/$PKGNAME/
|
||||||
mkdir -p /tmp/$PKGNAME/debian/
|
mkdir -p /tmp/$PKGNAME/debian/
|
||||||
mkdir -p /tmp/$PKGNAME/usr/bin/
|
mkdir -p /tmp/$PKGNAME/usr/bin/
|
||||||
mkdir -p /tmp/$PKGNAME/etc/systemd/system/
|
mkdir -p /tmp/$PKGNAME/lib/systemd/system/
|
||||||
|
|
||||||
cat > /tmp/$PKGNAME/debian/changelog << EOF
|
cat > /tmp/$PKGNAME/debian/changelog << EOF
|
||||||
Please see https://github.com/yggdrasil-network/yggdrasil-go/
|
Please see https://github.com/yggdrasil-network/yggdrasil-go/
|
||||||
|
@ -47,11 +50,12 @@ echo 9 > /tmp/$PKGNAME/debian/compat
|
||||||
cat > /tmp/$PKGNAME/debian/control << EOF
|
cat > /tmp/$PKGNAME/debian/control << EOF
|
||||||
Package: $PKGNAME
|
Package: $PKGNAME
|
||||||
Version: $PKGVERSION
|
Version: $PKGVERSION
|
||||||
Section: contrib/net
|
Section: golang
|
||||||
Priority: extra
|
Priority: optional
|
||||||
Architecture: $PKGARCH
|
Architecture: $PKGARCH
|
||||||
Replaces: $PKGREPLACES
|
Replaces: $PKGREPLACES
|
||||||
Conflicts: $PKGREPLACES
|
Conflicts: $PKGREPLACES
|
||||||
|
Depends: systemd
|
||||||
Maintainer: Neil Alexander <neilalexander@users.noreply.github.com>
|
Maintainer: Neil Alexander <neilalexander@users.noreply.github.com>
|
||||||
Description: Yggdrasil Network
|
Description: Yggdrasil Network
|
||||||
Yggdrasil is an early-stage implementation of a fully end-to-end encrypted IPv6
|
Yggdrasil is an early-stage implementation of a fully end-to-end encrypted IPv6
|
||||||
|
@ -68,35 +72,52 @@ EOF
|
||||||
cat > /tmp/$PKGNAME/debian/install << EOF
|
cat > /tmp/$PKGNAME/debian/install << EOF
|
||||||
usr/bin/yggdrasil usr/bin
|
usr/bin/yggdrasil usr/bin
|
||||||
usr/bin/yggdrasilctl usr/bin
|
usr/bin/yggdrasilctl usr/bin
|
||||||
etc/systemd/system/*.service etc/systemd/system
|
lib/systemd/system/*.service lib/systemd/system
|
||||||
EOF
|
EOF
|
||||||
cat > /tmp/$PKGNAME/debian/postinst << EOF
|
cat > /tmp/$PKGNAME/debian/postinst << EOF
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
if ! getent group yggdrasil 2>&1 > /dev/null; then
|
if ! getent group yggdrasil 2>&1 > /dev/null; then
|
||||||
groupadd --system --force yggdrasil || echo "Failed to create group 'yggdrasil' - please create it manually and reinstall"
|
groupadd --system --force yggdrasil
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f /etc/yggdrasil.conf ];
|
if [ ! -d /etc/yggdrasil ];
|
||||||
|
then
|
||||||
|
mkdir -p /etc/yggdrasil
|
||||||
|
chown root:yggdrasil /etc/yggdrasil
|
||||||
|
chmod 750 /etc/yggdrasil
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f /etc/yggdrasil/yggdrasil.conf ];
|
||||||
|
then
|
||||||
|
test -f /etc/yggdrasil.conf && mv /etc/yggdrasil.conf /etc/yggdrasil/yggdrasil.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f /etc/yggdrasil/yggdrasil.conf ];
|
||||||
then
|
then
|
||||||
mkdir -p /var/backups
|
mkdir -p /var/backups
|
||||||
echo "Backing up configuration file to /var/backups/yggdrasil.conf.`date +%Y%m%d`"
|
echo "Backing up configuration file to /var/backups/yggdrasil.conf.`date +%Y%m%d`"
|
||||||
cp /etc/yggdrasil.conf /var/backups/yggdrasil.conf.`date +%Y%m%d`
|
cp /etc/yggdrasil/yggdrasil.conf /var/backups/yggdrasil.conf.`date +%Y%m%d`
|
||||||
echo "Normalising and updating /etc/yggdrasil.conf"
|
|
||||||
/usr/bin/yggdrasil -useconf -normaliseconf < /var/backups/yggdrasil.conf.`date +%Y%m%d` > /etc/yggdrasil.conf
|
|
||||||
chgrp yggdrasil /etc/yggdrasil.conf
|
|
||||||
|
|
||||||
if command -v systemctl >/dev/null; then
|
echo "Normalising and updating /etc/yggdrasil/yggdrasil.conf"
|
||||||
systemctl daemon-reload >/dev/null || true
|
/usr/bin/yggdrasil -useconf -normaliseconf < /var/backups/yggdrasil.conf.`date +%Y%m%d` > /etc/yggdrasil/yggdrasil.conf
|
||||||
systemctl enable yggdrasil || true
|
|
||||||
systemctl start yggdrasil || true
|
chown root:yggdrasil /etc/yggdrasil/yggdrasil.conf
|
||||||
fi
|
chmod 640 /etc/yggdrasil/yggdrasil.conf
|
||||||
else
|
else
|
||||||
echo "Generating initial configuration file /etc/yggdrasil.conf"
|
echo "Generating initial configuration file /etc/yggdrasil/yggdrasil.conf"
|
||||||
echo "Please familiarise yourself with this file before starting Yggdrasil"
|
/usr/bin/yggdrasil -genconf > /etc/yggdrasil/yggdrasil.conf
|
||||||
sh -c 'umask 0027 && /usr/bin/yggdrasil -genconf > /etc/yggdrasil.conf'
|
|
||||||
chgrp yggdrasil /etc/yggdrasil.conf
|
chown root:yggdrasil /etc/yggdrasil/yggdrasil.conf
|
||||||
|
chmod 640 /etc/yggdrasil/yggdrasil.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
systemctl enable yggdrasil
|
||||||
|
systemctl restart yggdrasil
|
||||||
|
|
||||||
|
exit 0
|
||||||
EOF
|
EOF
|
||||||
cat > /tmp/$PKGNAME/debian/prerm << EOF
|
cat > /tmp/$PKGNAME/debian/prerm << EOF
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
@ -110,13 +131,14 @@ EOF
|
||||||
|
|
||||||
cp yggdrasil /tmp/$PKGNAME/usr/bin/
|
cp yggdrasil /tmp/$PKGNAME/usr/bin/
|
||||||
cp yggdrasilctl /tmp/$PKGNAME/usr/bin/
|
cp yggdrasilctl /tmp/$PKGNAME/usr/bin/
|
||||||
cp contrib/systemd/*.service /tmp/$PKGNAME/etc/systemd/system/
|
cp contrib/systemd/yggdrasil-default-config.service.debian /tmp/$PKGNAME/lib/systemd/system/yggdrasil-default-config.service
|
||||||
|
cp contrib/systemd/yggdrasil.service.debian /tmp/$PKGNAME/lib/systemd/system/yggdrasil.service
|
||||||
|
|
||||||
tar -czvf /tmp/$PKGNAME/data.tar.gz -C /tmp/$PKGNAME/ \
|
tar --no-xattrs -czvf /tmp/$PKGNAME/data.tar.gz -C /tmp/$PKGNAME/ \
|
||||||
usr/bin/yggdrasil usr/bin/yggdrasilctl \
|
usr/bin/yggdrasil usr/bin/yggdrasilctl \
|
||||||
etc/systemd/system/yggdrasil.service \
|
lib/systemd/system/yggdrasil.service \
|
||||||
etc/systemd/system/yggdrasil-default-config.service
|
lib/systemd/system/yggdrasil-default-config.service
|
||||||
tar -czvf /tmp/$PKGNAME/control.tar.gz -C /tmp/$PKGNAME/debian .
|
tar --no-xattrs -czvf /tmp/$PKGNAME/control.tar.gz -C /tmp/$PKGNAME/debian .
|
||||||
echo 2.0 > /tmp/$PKGNAME/debian-binary
|
echo 2.0 > /tmp/$PKGNAME/debian-binary
|
||||||
|
|
||||||
ar -r $PKGFILE \
|
ar -r $PKGFILE \
|
||||||
|
|
|
@ -7,6 +7,7 @@ set -ef
|
||||||
PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/version}
|
PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/version}
|
||||||
PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)}
|
PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)}
|
||||||
PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)}
|
PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)}
|
||||||
|
GOVER=$(go version | { read _ _ version _; echo ${version#go}; })
|
||||||
|
|
||||||
LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER"
|
LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER"
|
||||||
ARGS="-v"
|
ARGS="-v"
|
||||||
|
@ -33,11 +34,20 @@ if [ ! $IOS ] && [ ! $ANDROID ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
ver_le() {
|
||||||
|
printf "$1\n$2\n" | sort -VC
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ $ANDROID ] && ver_le 1.23.0 $GOVER ; then
|
||||||
|
# github.com/wlynxg/anet library relies on //go:linkname
|
||||||
|
LDFLAGS="$LDFLAGS -checklinkname=0"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ $IOS ]; then
|
if [ $IOS ]; then
|
||||||
echo "Building framework for iOS"
|
echo "Building framework for iOS"
|
||||||
go get golang.org/x/mobile/bind
|
go get golang.org/x/mobile/bind
|
||||||
gomobile bind \
|
gomobile bind \
|
||||||
-target ios -tags mobile -o Yggdrasil.xcframework \
|
-target ios,macos -tags mobile -o Yggdrasil.xcframework \
|
||||||
-ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \
|
-ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \
|
||||||
./contrib/mobile ./src/config;
|
./contrib/mobile ./src/config;
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package mobile
|
package mobile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
@ -12,12 +12,10 @@ import (
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc"
|
"github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/tun"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/version"
|
"github.com/yggdrasil-network/yggdrasil-go/src/version"
|
||||||
|
|
||||||
_ "golang.org/x/mobile/bind"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Yggdrasil mobile package is meant to "plug the gap" for mobile support, as
|
// Yggdrasil mobile package is meant to "plug the gap" for mobile support, as
|
||||||
|
@ -30,7 +28,9 @@ type Yggdrasil struct {
|
||||||
iprwc *ipv6rwc.ReadWriteCloser
|
iprwc *ipv6rwc.ReadWriteCloser
|
||||||
config *config.NodeConfig
|
config *config.NodeConfig
|
||||||
multicast *multicast.Multicast
|
multicast *multicast.Multicast
|
||||||
|
tun *tun.TunAdapter // optional
|
||||||
log MobileLogger
|
log MobileLogger
|
||||||
|
logger *log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartAutoconfigure starts a node with a randomly generated config
|
// StartAutoconfigure starts a node with a randomly generated config
|
||||||
|
@ -41,21 +41,28 @@ func (m *Yggdrasil) StartAutoconfigure() error {
|
||||||
// StartJSON starts a node with the given JSON config. You can get JSON config
|
// StartJSON starts a node with the given JSON config. You can get JSON config
|
||||||
// (rather than HJSON) by using the GenerateConfigJSON() function
|
// (rather than HJSON) by using the GenerateConfigJSON() function
|
||||||
func (m *Yggdrasil) StartJSON(configjson []byte) error {
|
func (m *Yggdrasil) StartJSON(configjson []byte) error {
|
||||||
|
setMemLimitIfPossible()
|
||||||
|
|
||||||
logger := log.New(m.log, "", 0)
|
logger := log.New(m.log, "", 0)
|
||||||
logger.EnableLevel("error")
|
logger.EnableLevel("error")
|
||||||
logger.EnableLevel("warn")
|
logger.EnableLevel("warn")
|
||||||
logger.EnableLevel("info")
|
logger.EnableLevel("info")
|
||||||
m.config = defaults.GenerateConfig()
|
m.logger = logger
|
||||||
if err := json.Unmarshal(configjson, &m.config); err != nil {
|
m.config = config.GenerateConfig()
|
||||||
|
if err := m.config.UnmarshalHJSON(configjson); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Setup the Yggdrasil node itself.
|
// Set up the Yggdrasil node itself.
|
||||||
{
|
{
|
||||||
sk, err := hex.DecodeString(m.config.PrivateKey)
|
iprange := net.IPNet{
|
||||||
if err != nil {
|
IP: net.ParseIP("200::"),
|
||||||
panic(err)
|
Mask: net.CIDRMask(7, 128),
|
||||||
|
}
|
||||||
|
options := []core.SetupOption{
|
||||||
|
core.PeerFilter(func(ip net.IP) bool {
|
||||||
|
return !iprange.Contains(ip)
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
options := []core.SetupOption{}
|
|
||||||
for _, peer := range m.config.Peers {
|
for _, peer := range m.config.Peers {
|
||||||
options = append(options, core.Peer{URI: peer})
|
options = append(options, core.Peer{URI: peer})
|
||||||
}
|
}
|
||||||
|
@ -71,15 +78,24 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
|
||||||
}
|
}
|
||||||
options = append(options, core.AllowedPublicKey(k[:]))
|
options = append(options, core.AllowedPublicKey(k[:]))
|
||||||
}
|
}
|
||||||
m.core, err = core.New(sk[:], logger, options...)
|
for _, lAddr := range m.config.Listen {
|
||||||
|
options = append(options, core.ListenAddress(lAddr))
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
m.core, err = core.New(m.config.Certificate, logger, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
address, subnet := m.core.Address(), m.core.Subnet()
|
||||||
|
logger.Infof("Your public key is %s", hex.EncodeToString(m.core.PublicKey()))
|
||||||
|
logger.Infof("Your IPv6 address is %s", address.String())
|
||||||
|
logger.Infof("Your IPv6 subnet is %s", subnet.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the multicast module.
|
// Set up the multicast module.
|
||||||
if len(m.config.MulticastInterfaces) > 0 {
|
if len(m.config.MulticastInterfaces) > 0 {
|
||||||
var err error
|
var err error
|
||||||
|
logger.Infof("Initializing multicast %s", "")
|
||||||
options := []multicast.SetupOption{}
|
options := []multicast.SetupOption{}
|
||||||
for _, intf := range m.config.MulticastInterfaces {
|
for _, intf := range m.config.MulticastInterfaces {
|
||||||
options = append(options, multicast.MulticastInterface{
|
options = append(options, multicast.MulticastInterface{
|
||||||
|
@ -88,9 +104,11 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
|
||||||
Listen: intf.Listen,
|
Listen: intf.Listen,
|
||||||
Port: intf.Port,
|
Port: intf.Port,
|
||||||
Priority: uint8(intf.Priority),
|
Priority: uint8(intf.Priority),
|
||||||
|
Password: intf.Password,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
m.multicast, err = multicast.New(m.core, logger, options...)
|
logger.Infof("Starting multicast %s", "")
|
||||||
|
m.multicast, err = multicast.New(m.core, m.logger, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorln("An error occurred starting multicast:", err)
|
logger.Errorln("An error occurred starting multicast:", err)
|
||||||
}
|
}
|
||||||
|
@ -151,10 +169,20 @@ func (m *Yggdrasil) RecvBuffer(buf []byte) (int, error) {
|
||||||
func (m *Yggdrasil) Stop() error {
|
func (m *Yggdrasil) Stop() error {
|
||||||
logger := log.New(m.log, "", 0)
|
logger := log.New(m.log, "", 0)
|
||||||
logger.EnableLevel("info")
|
logger.EnableLevel("info")
|
||||||
logger.Infof("Stop the mobile Yggdrasil instance %s", "")
|
logger.Infof("Stopping the mobile Yggdrasil instance %s", "")
|
||||||
if err := m.multicast.Stop(); err != nil {
|
if m.multicast != nil {
|
||||||
return err
|
logger.Infof("Stopping multicast %s", "")
|
||||||
|
if err := m.multicast.Stop(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
logger.Infof("Stopping TUN device %s", "")
|
||||||
|
if m.tun != nil {
|
||||||
|
if err := m.tun.Stop(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Infof("Stopping Yggdrasil core %s", "")
|
||||||
m.core.Stop()
|
m.core.Stop()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -166,7 +194,7 @@ func (m *Yggdrasil) RetryPeersNow() {
|
||||||
|
|
||||||
// GenerateConfigJSON generates mobile-friendly configuration in JSON format
|
// GenerateConfigJSON generates mobile-friendly configuration in JSON format
|
||||||
func GenerateConfigJSON() []byte {
|
func GenerateConfigJSON() []byte {
|
||||||
nc := defaults.GenerateConfig()
|
nc := config.GenerateConfig()
|
||||||
nc.IfName = "none"
|
nc.IfName = "none"
|
||||||
if json, err := json.Marshal(nc); err == nil {
|
if json, err := json.Marshal(nc); err == nil {
|
||||||
return json
|
return json
|
||||||
|
@ -191,9 +219,9 @@ func (m *Yggdrasil) GetPublicKeyString() string {
|
||||||
return hex.EncodeToString(m.core.GetSelf().Key)
|
return hex.EncodeToString(m.core.GetSelf().Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCoordsString gets the node's coordinates
|
// GetRoutingEntries gets the number of entries in the routing table
|
||||||
func (m *Yggdrasil) GetCoordsString() string {
|
func (m *Yggdrasil) GetRoutingEntries() int {
|
||||||
return fmt.Sprintf("%v", m.core.GetSelf().Coords)
|
return int(m.core.GetSelf().RoutingEntries)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Yggdrasil) GetPeersJSON() (result string) {
|
func (m *Yggdrasil) GetPeersJSON() (result string) {
|
||||||
|
@ -202,8 +230,11 @@ func (m *Yggdrasil) GetPeersJSON() (result string) {
|
||||||
IP string
|
IP string
|
||||||
}{}
|
}{}
|
||||||
for _, v := range m.core.GetPeers() {
|
for _, v := range m.core.GetPeers() {
|
||||||
a := address.AddrForKey(v.Key)
|
var ip string
|
||||||
ip := net.IP(a[:]).String()
|
if v.Key != nil {
|
||||||
|
a := address.AddrForKey(v.Key)
|
||||||
|
ip = net.IP(a[:]).String()
|
||||||
|
}
|
||||||
peers = append(peers, struct {
|
peers = append(peers, struct {
|
||||||
core.PeerInfo
|
core.PeerInfo
|
||||||
IP string
|
IP string
|
||||||
|
@ -219,8 +250,16 @@ func (m *Yggdrasil) GetPeersJSON() (result string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Yggdrasil) GetDHTJSON() (result string) {
|
func (m *Yggdrasil) GetPathsJSON() (result string) {
|
||||||
if res, err := json.Marshal(m.core.GetDHT()); err == nil {
|
if res, err := json.Marshal(m.core.GetPaths()); err == nil {
|
||||||
|
return string(res)
|
||||||
|
} else {
|
||||||
|
return "{}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Yggdrasil) GetTreeJSON() (result string) {
|
||||||
|
if res, err := json.Marshal(m.core.GetTree()); err == nil {
|
||||||
return string(res)
|
return string(res)
|
||||||
} else {
|
} else {
|
||||||
return "{}"
|
return "{}"
|
||||||
|
@ -235,3 +274,28 @@ func (m *Yggdrasil) GetMTU() int {
|
||||||
func GetVersion() string {
|
func GetVersion() string {
|
||||||
return version.BuildVersion()
|
return version.BuildVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConfigSummary struct {
|
||||||
|
PublicKey string
|
||||||
|
IPv6Address string
|
||||||
|
IPv6Subnet string
|
||||||
|
}
|
||||||
|
|
||||||
|
func SummaryForConfig(b []byte) *ConfigSummary {
|
||||||
|
cfg := config.GenerateConfig()
|
||||||
|
if err := cfg.UnmarshalHJSON(b); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pub := ed25519.PrivateKey(cfg.PrivateKey).Public().(ed25519.PublicKey)
|
||||||
|
hpub := hex.EncodeToString(pub)
|
||||||
|
addr := net.IP(address.AddrForKey(pub)[:])
|
||||||
|
snet := net.IPNet{
|
||||||
|
IP: append(address.SubnetForKey(pub)[:], 0, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
Mask: net.CIDRMask(64, 128),
|
||||||
|
}
|
||||||
|
return &ConfigSummary{
|
||||||
|
PublicKey: hpub,
|
||||||
|
IPv6Address: addr.String(),
|
||||||
|
IPv6Subnet: snet.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//go:build ios
|
//go:build ios || darwin
|
||||||
// +build ios
|
// +build ios darwin
|
||||||
|
|
||||||
package mobile
|
package mobile
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ void Log(const char *text) {
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MobileLogger struct {
|
type MobileLogger struct {
|
||||||
|
@ -26,3 +28,13 @@ func (nsl MobileLogger) Write(p []byte) (n int, err error) {
|
||||||
C.Log(cstr)
|
C.Log(cstr)
|
||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Yggdrasil) TakeOverTUN(fd int32) error {
|
||||||
|
options := []tun.SetupOption{
|
||||||
|
tun.FileDescriptor(fd),
|
||||||
|
tun.InterfaceMTU(m.iprwc.MTU()),
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
m.tun, err = tun.New(m.iprwc, m.logger, options...)
|
||||||
|
return err
|
||||||
|
}
|
10
contrib/mobile/mobile_mem_go120.go
Normal file
10
contrib/mobile/mobile_mem_go120.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
//go:build go1.20
|
||||||
|
// +build go1.20
|
||||||
|
|
||||||
|
package mobile
|
||||||
|
|
||||||
|
import "runtime/debug"
|
||||||
|
|
||||||
|
func setMemLimitIfPossible() {
|
||||||
|
debug.SetMemoryLimit(1024 * 1024 * 40)
|
||||||
|
}
|
8
contrib/mobile/mobile_mem_other.go
Normal file
8
contrib/mobile/mobile_mem_other.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
//go:build !go1.20
|
||||||
|
// +build !go1.20
|
||||||
|
|
||||||
|
package mobile
|
||||||
|
|
||||||
|
func setMemLimitIfPossible() {
|
||||||
|
// not supported by this Go version
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
//go:build !android && !ios
|
//go:build !android && !ios && !darwin
|
||||||
// +build !android,!ios
|
// +build !android,!ios,!darwin
|
||||||
|
|
||||||
package mobile
|
package mobile
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,27 @@
|
||||||
package mobile
|
package mobile
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gologme/log"
|
||||||
|
)
|
||||||
|
|
||||||
func TestStartYggdrasil(t *testing.T) {
|
func TestStartYggdrasil(t *testing.T) {
|
||||||
ygg := &Yggdrasil{}
|
logger := log.New(os.Stdout, "", 0)
|
||||||
|
logger.EnableLevel("error")
|
||||||
|
logger.EnableLevel("warn")
|
||||||
|
logger.EnableLevel("info")
|
||||||
|
|
||||||
|
ygg := &Yggdrasil{
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
if err := ygg.StartAutoconfigure(); err != nil {
|
if err := ygg.StartAutoconfigure(); err != nil {
|
||||||
t.Fatalf("Failed to start Yggdrasil: %s", err)
|
t.Fatalf("Failed to start Yggdrasil: %s", err)
|
||||||
}
|
}
|
||||||
t.Log("Address:", ygg.GetAddressString())
|
t.Log("Address:", ygg.GetAddressString())
|
||||||
t.Log("Subnet:", ygg.GetSubnetString())
|
t.Log("Subnet:", ygg.GetSubnetString())
|
||||||
t.Log("Coords:", ygg.GetCoordsString())
|
t.Log("Routing entries:", ygg.GetRoutingEntries())
|
||||||
if err := ygg.Stop(); err != nil {
|
if err := ygg.Stop(); err != nil {
|
||||||
t.Fatalf("Failed to stop Yggdrasil: %s", err)
|
t.Fatalf("Failed to stop Yggdrasil: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# This script generates an MSI file for Yggdrasil for a given architecture. It
|
# This script generates an MSI file for Yggdrasil for a given architecture. It
|
||||||
# needs to run on Windows within MSYS2 and Go 1.17 or later must be installed on
|
# needs to run on Windows within MSYS2 and Go 1.21 or later must be installed on
|
||||||
# the system and within the PATH. This is ran currently by GitHub Actions (see
|
# the system and within the PATH. This is ran currently by GitHub Actions (see
|
||||||
# the workflows in the repository).
|
# the workflows in the repository).
|
||||||
#
|
#
|
||||||
|
@ -16,20 +16,7 @@ then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Download the wix tools!
|
# Download the wix tools!
|
||||||
if [ ! -d wixbin ];
|
dotnet tool install --global wix --version 5.0.0
|
||||||
then
|
|
||||||
curl -LO https://wixtoolset.org/downloads/v3.14.0.3910/wix314-binaries.zip
|
|
||||||
if [ `md5sum wix314-binaries.zip | cut -f 1 -d " "` != "34f655cf108086838dd5a76d4318063b" ];
|
|
||||||
then
|
|
||||||
echo "wix package didn't match expected checksum"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
mkdir -p wixbin
|
|
||||||
unzip -o wix314-binaries.zip -d wixbin || (
|
|
||||||
echo "failed to unzip WiX"
|
|
||||||
exit 1
|
|
||||||
)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build Yggdrasil!
|
# Build Yggdrasil!
|
||||||
[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build
|
[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build
|
||||||
|
@ -61,6 +48,11 @@ PKGVERSIONMS=$(echo $PKGVERSION | tr - .)
|
||||||
if [ ! -d wintun ];
|
if [ ! -d wintun ];
|
||||||
then
|
then
|
||||||
curl -o wintun.zip https://www.wintun.net/builds/wintun-0.14.1.zip
|
curl -o wintun.zip https://www.wintun.net/builds/wintun-0.14.1.zip
|
||||||
|
if [ `sha256sum wintun.zip | cut -f 1 -d " "` != "07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51" ];
|
||||||
|
then
|
||||||
|
echo "wintun package didn't match expected checksum"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
unzip wintun.zip
|
unzip wintun.zip
|
||||||
fi
|
fi
|
||||||
if [ $PKGARCH = "x64" ]; then
|
if [ $PKGARCH = "x64" ]; then
|
||||||
|
@ -101,7 +93,7 @@ cat > wix.xml << EOF
|
||||||
Description="Yggdrasil Network Installer"
|
Description="Yggdrasil Network Installer"
|
||||||
Comments="Yggdrasil Network standalone router for Windows."
|
Comments="Yggdrasil Network standalone router for Windows."
|
||||||
Manufacturer="github.com/yggdrasil-network"
|
Manufacturer="github.com/yggdrasil-network"
|
||||||
InstallerVersion="200"
|
InstallerVersion="500"
|
||||||
InstallScope="perMachine"
|
InstallScope="perMachine"
|
||||||
Languages="1033"
|
Languages="1033"
|
||||||
Compressed="yes"
|
Compressed="yes"
|
||||||
|
@ -205,5 +197,5 @@ EOF
|
||||||
# Generate the MSI
|
# Generate the MSI
|
||||||
CANDLEFLAGS="-nologo"
|
CANDLEFLAGS="-nologo"
|
||||||
LIGHTFLAGS="-nologo -spdb -sice:ICE71 -sice:ICE61"
|
LIGHTFLAGS="-nologo -spdb -sice:ICE71 -sice:ICE61"
|
||||||
wixbin/candle $CANDLEFLAGS -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj -arch ${PKGARCH} wix.xml && \
|
candle $CANDLEFLAGS -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj -arch ${PKGARCH} wix.xml && \
|
||||||
wixbin/light $LIGHTFLAGS -ext WixUtilExtension.dll -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.msi ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj
|
light $LIGHTFLAGS -ext WixUtilExtension.dll -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.msi ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj
|
||||||
|
|
|
@ -6,7 +6,6 @@ CONFFILE="/etc/yggdrasil.conf"
|
||||||
pidfile="/run/${RC_SVCNAME}.pid"
|
pidfile="/run/${RC_SVCNAME}.pid"
|
||||||
|
|
||||||
command="/usr/bin/yggdrasil"
|
command="/usr/bin/yggdrasil"
|
||||||
extra_started_commands="reload"
|
|
||||||
|
|
||||||
depend() {
|
depend() {
|
||||||
use net dns logger
|
use net dns logger
|
||||||
|
@ -42,12 +41,6 @@ start() {
|
||||||
eend $?
|
eend $?
|
||||||
}
|
}
|
||||||
|
|
||||||
reload() {
|
|
||||||
ebegin "Reloading ${RC_SVCNAME}"
|
|
||||||
start-stop-daemon --signal HUP --pidfile "${pidfile}"
|
|
||||||
eend $?
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
ebegin "Stopping ${RC_SVCNAME}"
|
ebegin "Stopping ${RC_SVCNAME}"
|
||||||
start-stop-daemon --stop --pidfile "${pidfile}" --exec "${command}"
|
start-stop-daemon --stop --pidfile "${pidfile}" --exec "${command}"
|
||||||
|
|
13
contrib/systemd/yggdrasil-default-config.service.debian
Normal file
13
contrib/systemd/yggdrasil-default-config.service.debian
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Yggdrasil default config generator
|
||||||
|
ConditionPathExists=|!/etc/yggdrasil/yggdrasil.conf
|
||||||
|
ConditionFileNotEmpty=|!/etc/yggdrasil/yggdrasil.conf
|
||||||
|
Wants=local-fs.target
|
||||||
|
After=local-fs.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
Group=yggdrasil
|
||||||
|
ExecStartPre=/usr/bin/mkdir -p /etc/yggdrasil
|
||||||
|
ExecStart=/usr/bin/yggdrasil -genconf > /etc/yggdrasil/yggdrasil.conf
|
||||||
|
ExecStartPost=/usr/bin/chmod -R 0640 /etc/yggdrasil
|
25
contrib/systemd/yggdrasil.service.debian
Normal file
25
contrib/systemd/yggdrasil.service.debian
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Yggdrasil Network
|
||||||
|
Wants=network-online.target
|
||||||
|
Wants=yggdrasil-default-config.service
|
||||||
|
After=network-online.target
|
||||||
|
After=yggdrasil-default-config.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Group=yggdrasil
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
NoNewPrivileges=true
|
||||||
|
RuntimeDirectory=yggdrasil
|
||||||
|
ReadWritePaths=/var/run/yggdrasil/ /run/yggdrasil/
|
||||||
|
SyslogIdentifier=yggdrasil
|
||||||
|
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||||
|
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||||
|
ExecStartPre=+-/sbin/modprobe tun
|
||||||
|
ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil/yggdrasil.conf
|
||||||
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
|
Restart=always
|
||||||
|
TimeoutStopSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
55
go.mod
55
go.mod
|
@ -1,38 +1,49 @@
|
||||||
module github.com/yggdrasil-network/yggdrasil-go
|
module github.com/yggdrasil-network/yggdrasil-go
|
||||||
|
|
||||||
go 1.17
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439
|
github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3
|
||||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979
|
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d
|
||||||
github.com/cheggaaa/pb/v3 v3.0.8
|
github.com/cheggaaa/pb/v3 v3.1.5
|
||||||
github.com/gologme/log v1.2.0
|
github.com/coder/websocket v1.8.12
|
||||||
|
github.com/gologme/log v1.3.0
|
||||||
github.com/hashicorp/go-syslog v1.0.0
|
github.com/hashicorp/go-syslog v1.0.0
|
||||||
github.com/hjson/hjson-go v3.1.0+incompatible
|
github.com/hjson/hjson-go/v4 v4.4.0
|
||||||
github.com/kardianos/minwinsvc v1.0.2
|
github.com/kardianos/minwinsvc v1.0.2
|
||||||
github.com/mitchellh/mapstructure v1.4.1
|
github.com/quic-go/quic-go v0.48.2
|
||||||
github.com/vishvananda/netlink v1.1.0
|
github.com/vishvananda/netlink v1.3.0
|
||||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099
|
github.com/wlynxg/anet v0.0.5
|
||||||
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b
|
golang.org/x/crypto v0.33.0
|
||||||
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43
|
golang.org/x/net v0.35.0
|
||||||
golang.org/x/text v0.3.8
|
golang.org/x/sys v0.30.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a
|
golang.org/x/text v0.22.0
|
||||||
golang.zx2c4.com/wireguard/windows v0.4.12
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
|
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
||||||
|
github.com/bits-and-blooms/bloom/v3 v3.7.0 // indirect
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||||
golang.org/x/tools v0.1.12 // indirect
|
golang.org/x/mod v0.19.0 // indirect
|
||||||
|
golang.org/x/sync v0.11.0 // indirect
|
||||||
|
golang.org/x/tools v0.23.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||||
github.com/fatih/color v1.12.0 // indirect
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.5
|
github.com/olekukonko/tablewriter v0.0.5
|
||||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
|
suah.dev/protect v1.2.4
|
||||||
)
|
)
|
||||||
|
|
217
go.sum
217
go.sum
|
@ -1,120 +1,119 @@
|
||||||
github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439 h1:eOW6/XIs06TnUn9GPCnfv71CQZw8edP3u3mH3lZt6iM=
|
github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3 h1:d8N0z+udAnbU5PdjpLSNPTWlqeU/nnYsQ42B6+879aw=
|
||||||
github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk=
|
github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3/go.mod h1:SrrElc3FFMpYCODSr11jWbLFeOM8WsY+DbDY/l2AXF0=
|
||||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ=
|
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM=
|
||||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI=
|
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
|
||||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||||
github.com/cheggaaa/pb/v3 v3.0.8 h1:bC8oemdChbke2FHIIGy9mn4DPJ2caZYQnfbRqwmdCoA=
|
github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA=
|
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
||||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
github.com/bits-and-blooms/bloom/v3 v3.7.0 h1:VfknkqV4xI+PsaDIsoHueyxVDZrfvMn56jeWUzvzdls=
|
||||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
github.com/bits-and-blooms/bloom/v3 v3.7.0/go.mod h1:VKlUSvp0lFIYqxJjzdnSsZEw4iHb1kOL2tfHTgyJBHg=
|
||||||
github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c=
|
github.com/cheggaaa/pb/v3 v3.1.5 h1:QuuUzeM2WsAqG2gMqtzaWithDJv0i+i6UlnwSCI4QLk=
|
||||||
github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
|
github.com/cheggaaa/pb/v3 v3.1.5/go.mod h1:CrxkeghYTXi1lQBEI7jSn+3svI3cuc19haAj6jM60XI=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||||
|
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
|
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
|
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/gologme/log v1.3.0 h1:l781G4dE+pbigClDSDzSaaYKtiueHCILUa/qSDsmHAo=
|
||||||
|
github.com/gologme/log v1.3.0/go.mod h1:yKT+DvIPdDdDoPtqFrFxheooyVmoqi0BAsw+erN3wA4=
|
||||||
|
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||||
|
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||||
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
|
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
github.com/hjson/hjson-go v3.1.0+incompatible h1:DY/9yE8ey8Zv22bY+mHV1uk2yRy0h8tKhZ77hEdi0Aw=
|
github.com/hjson/hjson-go/v4 v4.4.0 h1:D/NPvqOCH6/eisTb5/ztuIS8GUvmpHaLOcNk1Bjr298=
|
||||||
github.com/hjson/hjson-go v3.1.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
|
github.com/hjson/hjson-go/v4 v4.4.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/kardianos/minwinsvc v1.0.2 h1:JmZKFJQrmTGa/WiW+vkJXKmfzdjabuEW4Tirj5lLdR0=
|
github.com/kardianos/minwinsvc v1.0.2 h1:JmZKFJQrmTGa/WiW+vkJXKmfzdjabuEW4Tirj5lLdR0=
|
||||||
github.com/kardianos/minwinsvc v1.0.2/go.mod h1:LUZNYhNmxujx2tR7FbdxqYJ9XDDoCd3MQcl1o//FWl4=
|
github.com/kardianos/minwinsvc v1.0.2/go.mod h1:LUZNYhNmxujx2tR7FbdxqYJ9XDDoCd3MQcl1o//FWl4=
|
||||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
|
||||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
|
||||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
|
||||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||||
|
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||||
|
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
|
||||||
|
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
|
||||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||||
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg=
|
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||||
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099 h1:aIu0lKmfdgtn2uTj7JI2oN4TUrQvgB+wzTPO23bCKt8=
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
|
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/net v0.0.0-20210927181540-4e4d966f7476/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
suah.dev/protect v1.2.4 h1:iVZG/zQB63FKNpITDYM/cXoAeCTIjCiXHuFVByJFDzg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
suah.dev/protect v1.2.4/go.mod h1:vVrquYO3u1Ep9Ez2z8x+6N6/czm+TBmWKZfiXU2tb54=
|
||||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4=
|
|
||||||
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
|
|
||||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
|
||||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20211012062646-82d2aa87aa62/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU=
|
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a h1:tTbyylK9/D3u/wEP26Vx7L700UpY48nhioJWZM1vhZw=
|
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU=
|
|
||||||
golang.zx2c4.com/wireguard/windows v0.4.12 h1:CUmbdWKVNzTSsVb4yUAiEwL3KsabdJkEPdDjCHxBlhA=
|
|
||||||
golang.zx2c4.com/wireguard/windows v0.4.12/go.mod h1:PW4y+d9oY83XU9rRwRwrJDwEMuhVjMxu2gfD1cfzS7w=
|
|
||||||
|
|
|
@ -113,7 +113,7 @@ func SubnetForKey(publicKey ed25519.PublicKey) *Subnet {
|
||||||
return &snet
|
return &snet
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKet returns the partial ed25519.PublicKey for the Address.
|
// GetKey returns the partial ed25519.PublicKey for the Address.
|
||||||
// This is used for key lookup.
|
// This is used for key lookup.
|
||||||
func (a *Address) GetKey() ed25519.PublicKey {
|
func (a *Address) GetKey() ed25519.PublicKey {
|
||||||
var key [ed25519.PublicKeySize]byte
|
var key [ed25519.PublicKeySize]byte
|
||||||
|
@ -141,7 +141,7 @@ func (a *Address) GetKey() ed25519.PublicKey {
|
||||||
return ed25519.PublicKey(key[:])
|
return ed25519.PublicKey(key[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKet returns the partial ed25519.PublicKey for the Subnet.
|
// GetKey returns the partial ed25519.PublicKey for the Subnet.
|
||||||
// This is used for key lookup.
|
// This is used for key lookup.
|
||||||
func (s *Subnet) GetKey() ed25519.PublicKey {
|
func (s *Subnet) GetKey() ed25519.PublicKey {
|
||||||
var addr Address
|
var addr Address
|
||||||
|
|
|
@ -3,13 +3,13 @@ package address
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"math/rand"
|
"crypto/rand"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAddress_Address_IsValid(t *testing.T) {
|
func TestAddress_Address_IsValid(t *testing.T) {
|
||||||
var address Address
|
var address Address
|
||||||
rand.Read(address[:])
|
_, _ = rand.Read(address[:])
|
||||||
|
|
||||||
address[0] = 0
|
address[0] = 0
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ func TestAddress_Address_IsValid(t *testing.T) {
|
||||||
|
|
||||||
func TestAddress_Subnet_IsValid(t *testing.T) {
|
func TestAddress_Subnet_IsValid(t *testing.T) {
|
||||||
var subnet Subnet
|
var subnet Subnet
|
||||||
rand.Read(subnet[:])
|
_, _ = rand.Read(subnet[:])
|
||||||
|
|
||||||
subnet[0] = 0
|
subnet[0] = 0
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
type AddPeerRequest struct {
|
type AddPeerRequest struct {
|
||||||
Uri string `json:"uri"`
|
Uri string `json:"uri"`
|
||||||
Sintf string `json:"interface,omitempty"`
|
Sintf string `json:"interface,omitempty"`
|
||||||
|
@ -7,6 +12,10 @@ type AddPeerRequest struct {
|
||||||
|
|
||||||
type AddPeerResponse struct{}
|
type AddPeerResponse struct{}
|
||||||
|
|
||||||
func (a *AdminSocket) addPeerHandler(req *AddPeerRequest, res *AddPeerResponse) error {
|
func (a *AdminSocket) addPeerHandler(req *AddPeerRequest, _ *AddPeerResponse) error {
|
||||||
return a.core.AddPeer(req.Uri, req.Sintf)
|
u, err := url.Parse(req.Uri)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse peering URI: %w", err)
|
||||||
|
}
|
||||||
|
return a.core.AddPeer(u, req.Sintf)
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,10 +35,10 @@ type AdminSocketRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdminSocketResponse struct {
|
type AdminSocketResponse struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
Request json.RawMessage `json:"request"`
|
Request AdminSocketRequest `json:"request"`
|
||||||
Response json.RawMessage `json:"response"`
|
Response json.RawMessage `json:"response"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
|
@ -83,6 +83,52 @@ func New(c *core.Core, log core.Logger, opts ...SetupOption) (*AdminSocket, erro
|
||||||
if a.config.listenaddr == "none" || a.config.listenaddr == "" {
|
if a.config.listenaddr == "none" || a.config.listenaddr == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listenaddr := string(a.config.listenaddr)
|
||||||
|
u, err := url.Parse(listenaddr)
|
||||||
|
if err == nil {
|
||||||
|
switch strings.ToLower(u.Scheme) {
|
||||||
|
case "unix":
|
||||||
|
if _, err := os.Stat(u.Path); err == nil {
|
||||||
|
a.log.Debugln("Admin socket", u.Path, "already exists, trying to clean up")
|
||||||
|
if _, err := net.DialTimeout("unix", u.Path, time.Second*2); err == nil || err.(net.Error).Timeout() {
|
||||||
|
a.log.Errorln("Admin socket", u.Path, "already exists and is in use by another process")
|
||||||
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
if err := os.Remove(u.Path); err == nil {
|
||||||
|
a.log.Debugln(u.Path, "was cleaned up")
|
||||||
|
} else {
|
||||||
|
a.log.Errorln(u.Path, "already exists and was not cleaned up:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.listener, err = net.Listen("unix", u.Path)
|
||||||
|
if err == nil {
|
||||||
|
switch u.Path[:1] {
|
||||||
|
case "@": // maybe abstract namespace
|
||||||
|
default:
|
||||||
|
if err := os.Chmod(u.Path, 0660); err != nil {
|
||||||
|
a.log.Warnln("WARNING:", u.Path, "may have unsafe permissions!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "tcp":
|
||||||
|
a.listener, err = net.Listen("tcp", u.Host)
|
||||||
|
default:
|
||||||
|
a.listener, err = net.Listen("tcp", listenaddr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
a.listener, err = net.Listen("tcp", listenaddr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
a.log.Errorf("Admin socket failed to listen: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
a.log.Infof("%s admin socket listening on %s",
|
||||||
|
strings.ToUpper(a.listener.Addr().Network()),
|
||||||
|
a.listener.Addr().String())
|
||||||
|
|
||||||
_ = a.AddHandler("list", "List available commands", []string{}, func(_ json.RawMessage) (interface{}, error) {
|
_ = a.AddHandler("list", "List available commands", []string{}, func(_ json.RawMessage) (interface{}, error) {
|
||||||
res := &ListResponse{}
|
res := &ListResponse{}
|
||||||
for name, handler := range a.handlers {
|
for name, handler := range a.handlers {
|
||||||
|
@ -132,14 +178,14 @@ func (a *AdminSocket) SetupAdminHandlers() {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
_ = a.AddHandler(
|
_ = a.AddHandler(
|
||||||
"getDHT", "Show known DHT entries", []string{},
|
"getTree", "Show known Tree entries", []string{},
|
||||||
func(in json.RawMessage) (interface{}, error) {
|
func(in json.RawMessage) (interface{}, error) {
|
||||||
req := &GetDHTRequest{}
|
req := &GetTreeRequest{}
|
||||||
res := &GetDHTResponse{}
|
res := &GetTreeResponse{}
|
||||||
if err := json.Unmarshal(in, &req); err != nil {
|
if err := json.Unmarshal(in, &req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := a.getDHTHandler(req, res); err != nil {
|
if err := a.getTreeHandler(req, res); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
|
@ -201,10 +247,6 @@ func (a *AdminSocket) SetupAdminHandlers() {
|
||||||
return res, nil
|
return res, nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
//_ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler)
|
|
||||||
//_ = a.AddHandler("debug_remoteGetSelf", []string{"key"}, t.proto.getSelfHandler)
|
|
||||||
//_ = a.AddHandler("debug_remoteGetPeers", []string{"key"}, t.proto.getPeersHandler)
|
|
||||||
//_ = a.AddHandler("debug_remoteGetDHT", []string{"key"}, t.proto.getDHTHandler)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsStarted returns true if the module has been started.
|
// IsStarted returns true if the module has been started.
|
||||||
|
@ -237,51 +279,6 @@ func (a *AdminSocket) Stop() error {
|
||||||
|
|
||||||
// listen is run by start and manages API connections.
|
// listen is run by start and manages API connections.
|
||||||
func (a *AdminSocket) listen() {
|
func (a *AdminSocket) listen() {
|
||||||
listenaddr := string(a.config.listenaddr)
|
|
||||||
u, err := url.Parse(listenaddr)
|
|
||||||
if err == nil {
|
|
||||||
switch strings.ToLower(u.Scheme) {
|
|
||||||
case "unix":
|
|
||||||
if _, err := os.Stat(listenaddr[7:]); err == nil {
|
|
||||||
a.log.Debugln("Admin socket", listenaddr[7:], "already exists, trying to clean up")
|
|
||||||
if _, err := net.DialTimeout("unix", listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() {
|
|
||||||
a.log.Errorln("Admin socket", listenaddr[7:], "already exists and is in use by another process")
|
|
||||||
os.Exit(1)
|
|
||||||
} else {
|
|
||||||
if err := os.Remove(listenaddr[7:]); err == nil {
|
|
||||||
a.log.Debugln(listenaddr[7:], "was cleaned up")
|
|
||||||
} else {
|
|
||||||
a.log.Errorln(listenaddr[7:], "already exists and was not cleaned up:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a.listener, err = net.Listen("unix", listenaddr[7:])
|
|
||||||
if err == nil {
|
|
||||||
switch listenaddr[7:8] {
|
|
||||||
case "@": // maybe abstract namespace
|
|
||||||
default:
|
|
||||||
if err := os.Chmod(listenaddr[7:], 0660); err != nil {
|
|
||||||
a.log.Warnln("WARNING:", listenaddr[:7], "may have unsafe permissions!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "tcp":
|
|
||||||
a.listener, err = net.Listen("tcp", u.Host)
|
|
||||||
default:
|
|
||||||
// err = errors.New(fmt.Sprint("protocol not supported: ", u.Scheme))
|
|
||||||
a.listener, err = net.Listen("tcp", listenaddr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
a.listener, err = net.Listen("tcp", listenaddr)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
a.log.Errorf("Admin socket failed to listen: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
a.log.Infof("%s admin socket listening on %s",
|
|
||||||
strings.ToUpper(a.listener.Addr().Network()),
|
|
||||||
a.listener.Addr().String())
|
|
||||||
defer a.listener.Close()
|
defer a.listener.Close()
|
||||||
for {
|
for {
|
||||||
conn, err := a.listener.Accept()
|
conn, err := a.listener.Accept()
|
||||||
|
@ -309,19 +306,6 @@ func (a *AdminSocket) handleRequest(conn net.Conn) {
|
||||||
|
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
defer func() {
|
|
||||||
r := recover()
|
|
||||||
if r != nil {
|
|
||||||
a.log.Debugln("Admin socket error:", r)
|
|
||||||
if err := encoder.Encode(&ErrorResponse{
|
|
||||||
Error: "Check your syntax and input types",
|
|
||||||
}); err != nil {
|
|
||||||
a.log.Debugln("Admin socket JSON encode error:", err)
|
|
||||||
}
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var err error
|
var err error
|
||||||
var buf json.RawMessage
|
var buf json.RawMessage
|
||||||
|
@ -335,6 +319,7 @@ func (a *AdminSocket) handleRequest(conn net.Conn) {
|
||||||
if err = json.Unmarshal(buf, &req); err != nil {
|
if err = json.Unmarshal(buf, &req); err != nil {
|
||||||
return fmt.Errorf("Failed to unmarshal request")
|
return fmt.Errorf("Failed to unmarshal request")
|
||||||
}
|
}
|
||||||
|
resp.Request = req
|
||||||
if req.Name == "" {
|
if req.Name == "" {
|
||||||
return fmt.Errorf("No request specified")
|
return fmt.Errorf("No request specified")
|
||||||
}
|
}
|
||||||
|
@ -371,13 +356,15 @@ type DataUnit uint64
|
||||||
|
|
||||||
func (d DataUnit) String() string {
|
func (d DataUnit) String() string {
|
||||||
switch {
|
switch {
|
||||||
case d > 1024*1024*1024*1024:
|
case d >= 1024*1024*1024*1024:
|
||||||
return fmt.Sprintf("%2.ftb", float64(d)/1024/1024/1024/1024)
|
return fmt.Sprintf("%2.1fTB", float64(d)/1024/1024/1024/1024)
|
||||||
case d > 1024*1024*1024:
|
case d >= 1024*1024*1024:
|
||||||
return fmt.Sprintf("%2.fgb", float64(d)/1024/1024/1024)
|
return fmt.Sprintf("%2.1fGB", float64(d)/1024/1024/1024)
|
||||||
case d > 1024*1024:
|
case d >= 1024*1024:
|
||||||
return fmt.Sprintf("%2.fmb", float64(d)/1024/1024)
|
return fmt.Sprintf("%2.1fMB", float64(d)/1024/1024)
|
||||||
|
case d >= 100:
|
||||||
|
return fmt.Sprintf("%2.1fKB", float64(d)/1024)
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("%2.fkb", float64(d)/1024)
|
return fmt.Sprintf("%dB", d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
package admin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"net"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GetDHTRequest struct{}
|
|
||||||
|
|
||||||
type GetDHTResponse struct {
|
|
||||||
DHT []DHTEntry `json:"dht"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DHTEntry struct {
|
|
||||||
IPAddress string `json:"address"`
|
|
||||||
PublicKey string `json:"key"`
|
|
||||||
Port uint64 `json:"port"`
|
|
||||||
Rest uint64 `json:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AdminSocket) getDHTHandler(req *GetDHTRequest, res *GetDHTResponse) error {
|
|
||||||
dht := a.core.GetDHT()
|
|
||||||
res.DHT = make([]DHTEntry, 0, len(dht))
|
|
||||||
for _, d := range dht {
|
|
||||||
addr := address.AddrForKey(d.Key)
|
|
||||||
res.DHT = append(res.DHT, DHTEntry{
|
|
||||||
IPAddress: net.IP(addr[:]).String(),
|
|
||||||
PublicKey: hex.EncodeToString(d.Key[:]),
|
|
||||||
Port: d.Port,
|
|
||||||
Rest: d.Rest,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
sort.SliceStable(res.DHT, func(i, j int) bool {
|
|
||||||
return strings.Compare(res.DHT[i].PublicKey, res.DHT[j].PublicKey) < 0
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@ package admin
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
|
@ -20,9 +20,10 @@ type PathEntry struct {
|
||||||
IPAddress string `json:"address"`
|
IPAddress string `json:"address"`
|
||||||
PublicKey string `json:"key"`
|
PublicKey string `json:"key"`
|
||||||
Path []uint64 `json:"path"`
|
Path []uint64 `json:"path"`
|
||||||
|
Sequence uint64 `json:"sequence"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AdminSocket) getPathsHandler(req *GetPathsRequest, res *GetPathsResponse) error {
|
func (a *AdminSocket) getPathsHandler(_ *GetPathsRequest, res *GetPathsResponse) error {
|
||||||
paths := a.core.GetPaths()
|
paths := a.core.GetPaths()
|
||||||
res.Paths = make([]PathEntry, 0, len(paths))
|
res.Paths = make([]PathEntry, 0, len(paths))
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
|
@ -31,10 +32,11 @@ func (a *AdminSocket) getPathsHandler(req *GetPathsRequest, res *GetPathsRespons
|
||||||
IPAddress: net.IP(addr[:]).String(),
|
IPAddress: net.IP(addr[:]).String(),
|
||||||
PublicKey: hex.EncodeToString(p.Key),
|
PublicKey: hex.EncodeToString(p.Key),
|
||||||
Path: p.Path,
|
Path: p.Path,
|
||||||
|
Sequence: p.Sequence,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sort.SliceStable(res.Paths, func(i, j int) bool {
|
slices.SortStableFunc(res.Paths, func(a, b PathEntry) int {
|
||||||
return strings.Compare(res.Paths[i].PublicKey, res.Paths[j].PublicKey) < 0
|
return strings.Compare(a.PublicKey, b.PublicKey)
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@ package admin
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
)
|
)
|
||||||
|
@ -16,39 +18,74 @@ type GetPeersResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PeerEntry struct {
|
type PeerEntry struct {
|
||||||
IPAddress string `json:"address"`
|
URI string `json:"remote,omitempty"`
|
||||||
PublicKey string `json:"key"`
|
Up bool `json:"up"`
|
||||||
Port uint64 `json:"port"`
|
Inbound bool `json:"inbound"`
|
||||||
Priority uint64 `json:"priority"`
|
IPAddress string `json:"address,omitempty"`
|
||||||
Coords []uint64 `json:"coords"`
|
PublicKey string `json:"key"`
|
||||||
Remote string `json:"remote"`
|
Port uint64 `json:"port"`
|
||||||
RXBytes DataUnit `json:"bytes_recvd"`
|
Priority uint64 `json:"priority"`
|
||||||
TXBytes DataUnit `json:"bytes_sent"`
|
Cost uint64 `json:"cost"`
|
||||||
Uptime float64 `json:"uptime"`
|
RXBytes DataUnit `json:"bytes_recvd,omitempty"`
|
||||||
|
TXBytes DataUnit `json:"bytes_sent,omitempty"`
|
||||||
|
RXRate DataUnit `json:"rate_recvd,omitempty"`
|
||||||
|
TXRate DataUnit `json:"rate_sent,omitempty"`
|
||||||
|
Uptime float64 `json:"uptime,omitempty"`
|
||||||
|
Latency time.Duration `json:"latency,omitempty"`
|
||||||
|
LastErrorTime time.Duration `json:"last_error_time,omitempty"`
|
||||||
|
LastError string `json:"last_error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersResponse) error {
|
func (a *AdminSocket) getPeersHandler(_ *GetPeersRequest, res *GetPeersResponse) error {
|
||||||
peers := a.core.GetPeers()
|
peers := a.core.GetPeers()
|
||||||
res.Peers = make([]PeerEntry, 0, len(peers))
|
res.Peers = make([]PeerEntry, 0, len(peers))
|
||||||
for _, p := range peers {
|
for _, p := range peers {
|
||||||
addr := address.AddrForKey(p.Key)
|
peer := PeerEntry{
|
||||||
res.Peers = append(res.Peers, PeerEntry{
|
Port: p.Port,
|
||||||
IPAddress: net.IP(addr[:]).String(),
|
Up: p.Up,
|
||||||
PublicKey: hex.EncodeToString(p.Key),
|
Inbound: p.Inbound,
|
||||||
Port: p.Port,
|
Priority: uint64(p.Priority), // can't be uint8 thanks to gobind
|
||||||
Priority: uint64(p.Priority), // can't be uint8 thanks to gobind
|
Cost: p.Cost,
|
||||||
Coords: p.Coords,
|
URI: p.URI,
|
||||||
Remote: p.Remote,
|
RXBytes: DataUnit(p.RXBytes),
|
||||||
RXBytes: DataUnit(p.RXBytes),
|
TXBytes: DataUnit(p.TXBytes),
|
||||||
TXBytes: DataUnit(p.TXBytes),
|
RXRate: DataUnit(p.RXRate),
|
||||||
Uptime: p.Uptime.Seconds(),
|
TXRate: DataUnit(p.TXRate),
|
||||||
})
|
Uptime: p.Uptime.Seconds(),
|
||||||
}
|
|
||||||
sort.Slice(res.Peers, func(i, j int) bool {
|
|
||||||
if res.Peers[i].Port == res.Peers[j].Port {
|
|
||||||
return res.Peers[i].Priority < res.Peers[j].Priority
|
|
||||||
}
|
}
|
||||||
return res.Peers[i].Port < res.Peers[j].Port
|
if p.Latency > 0 {
|
||||||
|
peer.Latency = p.Latency
|
||||||
|
}
|
||||||
|
if addr := address.AddrForKey(p.Key); addr != nil {
|
||||||
|
peer.PublicKey = hex.EncodeToString(p.Key)
|
||||||
|
peer.IPAddress = net.IP(addr[:]).String()
|
||||||
|
}
|
||||||
|
if p.LastError != nil {
|
||||||
|
peer.LastError = p.LastError.Error()
|
||||||
|
peer.LastErrorTime = time.Since(p.LastErrorTime)
|
||||||
|
}
|
||||||
|
res.Peers = append(res.Peers, peer)
|
||||||
|
}
|
||||||
|
slices.SortStableFunc(res.Peers, func(a, b PeerEntry) int {
|
||||||
|
if !a.Inbound && b.Inbound {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if a.Inbound && !b.Inbound {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if d := strings.Compare(a.PublicKey, b.PublicKey); d != 0 {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
if d := a.Priority - b.Priority; d != 0 {
|
||||||
|
return int(d)
|
||||||
|
}
|
||||||
|
if d := a.Cost - b.Cost; d != 0 {
|
||||||
|
return int(d)
|
||||||
|
}
|
||||||
|
if d := a.Uptime - b.Uptime; d != 0 {
|
||||||
|
return int(d)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,15 @@ import (
|
||||||
type GetSelfRequest struct{}
|
type GetSelfRequest struct{}
|
||||||
|
|
||||||
type GetSelfResponse struct {
|
type GetSelfResponse struct {
|
||||||
BuildName string `json:"build_name"`
|
BuildName string `json:"build_name"`
|
||||||
BuildVersion string `json:"build_version"`
|
BuildVersion string `json:"build_version"`
|
||||||
PublicKey string `json:"key"`
|
PublicKey string `json:"key"`
|
||||||
IPAddress string `json:"address"`
|
IPAddress string `json:"address"`
|
||||||
Coords []uint64 `json:"coords"`
|
RoutingEntries uint64 `json:"routing_entries"`
|
||||||
Subnet string `json:"subnet"`
|
Subnet string `json:"subnet"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse) error {
|
func (a *AdminSocket) getSelfHandler(_ *GetSelfRequest, res *GetSelfResponse) error {
|
||||||
self := a.core.GetSelf()
|
self := a.core.GetSelf()
|
||||||
snet := a.core.Subnet()
|
snet := a.core.Subnet()
|
||||||
res.BuildName = version.BuildName()
|
res.BuildName = version.BuildName()
|
||||||
|
@ -25,6 +25,6 @@ func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse)
|
||||||
res.PublicKey = hex.EncodeToString(self.Key[:])
|
res.PublicKey = hex.EncodeToString(self.Key[:])
|
||||||
res.IPAddress = a.core.Address().String()
|
res.IPAddress = a.core.Address().String()
|
||||||
res.Subnet = snet.String()
|
res.Subnet = snet.String()
|
||||||
res.Coords = self.Coords
|
res.RoutingEntries = self.RoutingEntries
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package admin
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
|
@ -23,7 +23,7 @@ type SessionEntry struct {
|
||||||
Uptime float64 `json:"uptime"`
|
Uptime float64 `json:"uptime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AdminSocket) getSessionsHandler(req *GetSessionsRequest, res *GetSessionsResponse) error {
|
func (a *AdminSocket) getSessionsHandler(_ *GetSessionsRequest, res *GetSessionsResponse) error {
|
||||||
sessions := a.core.GetSessions()
|
sessions := a.core.GetSessions()
|
||||||
res.Sessions = make([]SessionEntry, 0, len(sessions))
|
res.Sessions = make([]SessionEntry, 0, len(sessions))
|
||||||
for _, s := range sessions {
|
for _, s := range sessions {
|
||||||
|
@ -36,8 +36,8 @@ func (a *AdminSocket) getSessionsHandler(req *GetSessionsRequest, res *GetSessio
|
||||||
Uptime: s.Uptime.Seconds(),
|
Uptime: s.Uptime.Seconds(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sort.SliceStable(res.Sessions, func(i, j int) bool {
|
slices.SortStableFunc(res.Sessions, func(a, b SessionEntry) int {
|
||||||
return strings.Compare(res.Sessions[i].PublicKey, res.Sessions[j].PublicKey) < 0
|
return strings.Compare(a.PublicKey, b.PublicKey)
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
41
src/admin/gettree.go
Normal file
41
src/admin/gettree.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"net"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetTreeRequest struct{}
|
||||||
|
|
||||||
|
type GetTreeResponse struct {
|
||||||
|
Tree []TreeEntry `json:"tree"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TreeEntry struct {
|
||||||
|
IPAddress string `json:"address"`
|
||||||
|
PublicKey string `json:"key"`
|
||||||
|
Parent string `json:"parent"`
|
||||||
|
Sequence uint64 `json:"sequence"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AdminSocket) getTreeHandler(_ *GetTreeRequest, res *GetTreeResponse) error {
|
||||||
|
tree := a.core.GetTree()
|
||||||
|
res.Tree = make([]TreeEntry, 0, len(tree))
|
||||||
|
for _, d := range tree {
|
||||||
|
addr := address.AddrForKey(d.Key)
|
||||||
|
res.Tree = append(res.Tree, TreeEntry{
|
||||||
|
IPAddress: net.IP(addr[:]).String(),
|
||||||
|
PublicKey: hex.EncodeToString(d.Key[:]),
|
||||||
|
Parent: hex.EncodeToString(d.Parent[:]),
|
||||||
|
Sequence: d.Sequence,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
slices.SortStableFunc(res.Tree, func(a, b TreeEntry) int {
|
||||||
|
return strings.Compare(a.PublicKey, b.PublicKey)
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,9 +1,24 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Arceliar/ironwood/network"
|
||||||
|
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
|
)
|
||||||
|
|
||||||
func (c *AdminSocket) _applyOption(opt SetupOption) {
|
func (c *AdminSocket) _applyOption(opt SetupOption) {
|
||||||
switch v := opt.(type) {
|
switch v := opt.(type) {
|
||||||
case ListenAddress:
|
case ListenAddress:
|
||||||
c.config.listenaddr = v
|
c.config.listenaddr = v
|
||||||
|
case LogLookups:
|
||||||
|
c.logLookups()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,3 +29,51 @@ type SetupOption interface {
|
||||||
type ListenAddress string
|
type ListenAddress string
|
||||||
|
|
||||||
func (a ListenAddress) isSetupOption() {}
|
func (a ListenAddress) isSetupOption() {}
|
||||||
|
|
||||||
|
type LogLookups struct{}
|
||||||
|
|
||||||
|
func (l LogLookups) isSetupOption() {}
|
||||||
|
|
||||||
|
func (a *AdminSocket) logLookups() {
|
||||||
|
type resi struct {
|
||||||
|
Address string `json:"addr"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
Path []uint64 `json:"path"`
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
Infos []resi `json:"infos"`
|
||||||
|
}
|
||||||
|
type info struct {
|
||||||
|
path []uint64
|
||||||
|
time time.Time
|
||||||
|
}
|
||||||
|
type edk [ed25519.PublicKeySize]byte
|
||||||
|
infos := make(map[edk]info)
|
||||||
|
var m sync.Mutex
|
||||||
|
a.core.PacketConn.PacketConn.Debug.SetDebugLookupLogger(func(l network.DebugLookupInfo) {
|
||||||
|
var k edk
|
||||||
|
copy(k[:], l.Key[:])
|
||||||
|
m.Lock()
|
||||||
|
infos[k] = info{path: l.Path, time: time.Now()}
|
||||||
|
m.Unlock()
|
||||||
|
})
|
||||||
|
_ = a.AddHandler(
|
||||||
|
"lookups", "Dump a record of lookups received in the past hour", []string{},
|
||||||
|
func(in json.RawMessage) (interface{}, error) {
|
||||||
|
m.Lock()
|
||||||
|
rs := make([]resi, 0, len(infos))
|
||||||
|
for k, v := range infos {
|
||||||
|
if time.Since(v.time) > 24*time.Hour {
|
||||||
|
// TODO? automatic cleanup, so we don't need to call lookups periodically to prevent leaks
|
||||||
|
delete(infos, k)
|
||||||
|
}
|
||||||
|
a := address.AddrForKey(ed25519.PublicKey(k[:]))
|
||||||
|
addr := net.IP(a[:]).String()
|
||||||
|
rs = append(rs, resi{Address: addr, Key: hex.EncodeToString(k[:]), Path: v.path, Time: v.time.Unix()})
|
||||||
|
}
|
||||||
|
m.Unlock()
|
||||||
|
return &res{Infos: rs}, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
type RemovePeerRequest struct {
|
type RemovePeerRequest struct {
|
||||||
Uri string `json:"uri"`
|
Uri string `json:"uri"`
|
||||||
Sintf string `json:"interface,omitempty"`
|
Sintf string `json:"interface,omitempty"`
|
||||||
|
@ -7,6 +12,10 @@ type RemovePeerRequest struct {
|
||||||
|
|
||||||
type RemovePeerResponse struct{}
|
type RemovePeerResponse struct{}
|
||||||
|
|
||||||
func (a *AdminSocket) removePeerHandler(req *RemovePeerRequest, res *RemovePeerResponse) error {
|
func (a *AdminSocket) removePeerHandler(req *RemovePeerRequest, _ *RemovePeerResponse) error {
|
||||||
return a.core.RemovePeer(req.Uri, req.Sintf)
|
u, err := url.Parse(req.Uri)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse peering URI: %w", err)
|
||||||
|
}
|
||||||
|
return a.core.RemovePeer(u, req.Sintf)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,44 +17,244 @@ configuration option that is not provided.
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hjson/hjson-go/v4"
|
||||||
|
"golang.org/x/text/encoding/unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NodeConfig is the main configuration structure, containing configuration
|
// NodeConfig is the main configuration structure, containing configuration
|
||||||
// options that are necessary for an Yggdrasil node to run. You will need to
|
// options that are necessary for an Yggdrasil node to run. You will need to
|
||||||
// supply one of these structs to the Yggdrasil core when starting a node.
|
// supply one of these structs to the Yggdrasil core when starting a node.
|
||||||
type NodeConfig struct {
|
type NodeConfig struct {
|
||||||
Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tls://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."`
|
PrivateKey KeyBytes `json:",omitempty" comment:"Your private key. DO NOT share this with anyone!"`
|
||||||
InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ \"tls://a.b.c.d:e\" ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."`
|
PrivateKeyPath string `json:",omitempty" comment:"The path to your private key file in PEM format."`
|
||||||
Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."`
|
Certificate *tls.Certificate `json:"-"`
|
||||||
AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
|
Peers []string `comment:"List of outbound peer connection strings (e.g. tls://a.b.c.d:e or\nsocks://a.b.c.d:e/f.g.h.i:j). Connection strings can contain options,\nsee https://yggdrasil-network.github.io/configurationref.html#peers.\nYggdrasil has no concept of bootstrap nodes - all network traffic\nwill transit peer connections. Therefore make sure to only peer with\nnearby nodes that have good connectivity and low latency. Avoid adding\npeers to this list from distant countries as this will worsen your\nnode's connectivity and performance considerably."`
|
||||||
MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Each entry in the list should be a json object which may\ncontain Regex, Beacon, Listen, and Port. Regex is a regular expression\nwhich is matched against an interface name, and interfaces use the\nfirst configuration that they match gainst. Beacon configures whether\nor not the node should send link-local multicast beacons to advertise\ntheir presence, while listening for incoming connections on Port.\nListen controls whether or not the node listens for multicast beacons\nand opens outgoing connections."`
|
InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ \"tls://a.b.c.d:e\" ] }.\nYou should only use this option if your machine is multi-homed and you\nwant to establish outbound peer connections on different interfaces.\nOtherwise you should use \"Peers\"."`
|
||||||
AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."`
|
Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nThis is not required if you wish to establish outbound peerings only.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."`
|
||||||
PublicKey string `comment:"Your public key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."`
|
AdminListen string `json:",omitempty" comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
|
||||||
PrivateKey string `comment:"Your private key. DO NOT share this with anyone!"`
|
MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Regex is a regular expression which is matched against an\ninterface name, and interfaces use the first configuration that they\nmatch against. Beacon controls whether or not your node advertises its\npresence to others, whereas Listen controls whether or not your node\nlistens out for and tries to connect to other advertising nodes. See\nhttps://yggdrasil-network.github.io/configurationref.html#multicastinterfaces\nfor more supported options."`
|
||||||
|
AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast.\nWARNING: THIS IS NOT A FIREWALL and DOES NOT limit who can reach\nopen ports or services running on your machine!"`
|
||||||
IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."`
|
IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."`
|
||||||
IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
|
IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
|
||||||
|
LogLookups bool `json:",omitempty"`
|
||||||
NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."`
|
NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."`
|
||||||
NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."`
|
NodeInfo map[string]interface{} `comment:"Optional nodeinfo. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MulticastInterfaceConfig struct {
|
type MulticastInterfaceConfig struct {
|
||||||
Regex string
|
Regex string
|
||||||
Beacon bool
|
Beacon bool
|
||||||
Listen bool
|
Listen bool
|
||||||
Port uint16
|
Port uint16 `json:",omitempty"`
|
||||||
Priority uint64 // really uint8, but gobind won't export it
|
Priority uint64 `json:",omitempty"` // really uint8, but gobind won't export it
|
||||||
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSigningKeys replaces the signing keypair in the NodeConfig with a new
|
// Generates default configuration and returns a pointer to the resulting
|
||||||
// signing keypair. The signing keys are used by the switch to derive the
|
// NodeConfig. This is used when outputting the -genconf parameter and also when
|
||||||
// structure of the spanning tree.
|
// using -autoconf.
|
||||||
func (cfg *NodeConfig) NewKeys() {
|
func GenerateConfig() *NodeConfig {
|
||||||
spub, spriv, err := ed25519.GenerateKey(nil)
|
// Get the defaults for the platform.
|
||||||
|
defaults := GetDefaults()
|
||||||
|
// Create a node configuration and populate it.
|
||||||
|
cfg := new(NodeConfig)
|
||||||
|
cfg.NewPrivateKey()
|
||||||
|
cfg.Listen = []string{}
|
||||||
|
cfg.AdminListen = defaults.DefaultAdminListen
|
||||||
|
cfg.Peers = []string{}
|
||||||
|
cfg.InterfacePeers = map[string][]string{}
|
||||||
|
cfg.AllowedPublicKeys = []string{}
|
||||||
|
cfg.MulticastInterfaces = defaults.DefaultMulticastInterfaces
|
||||||
|
cfg.IfName = defaults.DefaultIfName
|
||||||
|
cfg.IfMTU = defaults.DefaultIfMTU
|
||||||
|
cfg.NodeInfoPrivacy = false
|
||||||
|
if err := cfg.postprocessConfig(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *NodeConfig) ReadFrom(r io.Reader) (int64, error) {
|
||||||
|
conf, err := io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
n := int64(len(conf))
|
||||||
|
// If there's a byte order mark - which Windows 10 is now incredibly fond of
|
||||||
|
// throwing everywhere when it's converting things into UTF-16 for the hell
|
||||||
|
// of it - remove it and decode back down into UTF-8. This is necessary
|
||||||
|
// because hjson doesn't know what to do with UTF-16 and will panic
|
||||||
|
if bytes.Equal(conf[0:2], []byte{0xFF, 0xFE}) ||
|
||||||
|
bytes.Equal(conf[0:2], []byte{0xFE, 0xFF}) {
|
||||||
|
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
|
||||||
|
decoder := utf.NewDecoder()
|
||||||
|
conf, err = decoder.Bytes(conf)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Generate a new configuration - this gives us a set of sane defaults -
|
||||||
|
// then parse the configuration we loaded above on top of it. The effect
|
||||||
|
// of this is that any configuration item that is missing from the provided
|
||||||
|
// configuration will use a sane default.
|
||||||
|
*cfg = *GenerateConfig()
|
||||||
|
if err := cfg.UnmarshalHJSON(conf); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *NodeConfig) UnmarshalHJSON(b []byte) error {
|
||||||
|
if err := hjson.Unmarshal(b, cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return cfg.postprocessConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *NodeConfig) postprocessConfig() error {
|
||||||
|
if cfg.PrivateKeyPath != "" {
|
||||||
|
cfg.PrivateKey = nil
|
||||||
|
f, err := os.ReadFile(cfg.PrivateKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := cfg.UnmarshalPEMPrivateKey(f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case cfg.Certificate == nil:
|
||||||
|
// No self-signed certificate has been generated yet.
|
||||||
|
fallthrough
|
||||||
|
case !bytes.Equal(cfg.Certificate.PrivateKey.(ed25519.PrivateKey), cfg.PrivateKey):
|
||||||
|
// A self-signed certificate was generated but the private
|
||||||
|
// key has changed since then, possibly because a new config
|
||||||
|
// was parsed.
|
||||||
|
if err := cfg.GenerateSelfSignedCertificate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC5280 section 4.1.2.5
|
||||||
|
var notAfterNeverExpires = time.Date(9999, time.December, 31, 23, 59, 59, 0, time.UTC)
|
||||||
|
|
||||||
|
func (cfg *NodeConfig) GenerateSelfSignedCertificate() error {
|
||||||
|
key, err := cfg.MarshalPEMPrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert, err := cfg.MarshalPEMCertificate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tlsCert, err := tls.X509KeyPair(cert, key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg.Certificate = &tlsCert
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *NodeConfig) MarshalPEMCertificate() ([]byte, error) {
|
||||||
|
privateKey := ed25519.PrivateKey(cfg.PrivateKey)
|
||||||
|
publicKey := privateKey.Public().(ed25519.PublicKey)
|
||||||
|
|
||||||
|
cert := &x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: hex.EncodeToString(publicKey),
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: notAfterNeverExpires,
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
certbytes, err := x509.CreateCertificate(rand.Reader, cert, cert, publicKey, privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
block := &pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: certbytes,
|
||||||
|
}
|
||||||
|
return pem.EncodeToMemory(block), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *NodeConfig) NewPrivateKey() {
|
||||||
|
_, spriv, err := ed25519.GenerateKey(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
cfg.PublicKey = hex.EncodeToString(spub[:])
|
cfg.PrivateKey = KeyBytes(spriv)
|
||||||
cfg.PrivateKey = hex.EncodeToString(spriv[:])
|
}
|
||||||
|
|
||||||
|
func (cfg *NodeConfig) MarshalPEMPrivateKey() ([]byte, error) {
|
||||||
|
b, err := x509.MarshalPKCS8PrivateKey(ed25519.PrivateKey(cfg.PrivateKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal PKCS8 key: %w", err)
|
||||||
|
}
|
||||||
|
block := &pem.Block{
|
||||||
|
Type: "PRIVATE KEY",
|
||||||
|
Bytes: b,
|
||||||
|
}
|
||||||
|
return pem.EncodeToMemory(block), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *NodeConfig) UnmarshalPEMPrivateKey(b []byte) error {
|
||||||
|
p, _ := pem.Decode(b)
|
||||||
|
if p == nil {
|
||||||
|
return fmt.Errorf("failed to parse PEM file")
|
||||||
|
}
|
||||||
|
if p.Type != "PRIVATE KEY" {
|
||||||
|
return fmt.Errorf("unexpected PEM type %q", p.Type)
|
||||||
|
}
|
||||||
|
k, err := x509.ParsePKCS8PrivateKey(p.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal PKCS8 key: %w", err)
|
||||||
|
}
|
||||||
|
key, ok := k.(ed25519.PrivateKey)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("private key must be ed25519 key")
|
||||||
|
}
|
||||||
|
if len(key) != ed25519.PrivateKeySize {
|
||||||
|
return fmt.Errorf("unexpected ed25519 private key length")
|
||||||
|
}
|
||||||
|
cfg.PrivateKey = KeyBytes(key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyBytes []byte
|
||||||
|
|
||||||
|
func (k KeyBytes) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(hex.EncodeToString(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeyBytes) UnmarshalJSON(b []byte) error {
|
||||||
|
var s string
|
||||||
|
var err error
|
||||||
|
if err = json.Unmarshal(b, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*k, err = hex.DecodeString(s)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,54 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConfig_Keys(t *testing.T) {
|
func TestConfig_Keys(t *testing.T) {
|
||||||
var nodeConfig NodeConfig
|
/*
|
||||||
nodeConfig.NewKeys()
|
var nodeConfig NodeConfig
|
||||||
|
nodeConfig.NewKeys()
|
||||||
|
|
||||||
publicKey1, err := hex.DecodeString(nodeConfig.PublicKey)
|
publicKey1, err := hex.DecodeString(nodeConfig.PublicKey)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("can not decode generated public key")
|
t.Fatal("can not decode generated public key")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(publicKey1) == 0 {
|
if len(publicKey1) == 0 {
|
||||||
t.Fatal("empty public key generated")
|
t.Fatal("empty public key generated")
|
||||||
}
|
}
|
||||||
|
|
||||||
privateKey1, err := hex.DecodeString(nodeConfig.PrivateKey)
|
privateKey1, err := hex.DecodeString(nodeConfig.PrivateKey)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("can not decode generated private key")
|
t.Fatal("can not decode generated private key")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(privateKey1) == 0 {
|
if len(privateKey1) == 0 {
|
||||||
t.Fatal("empty private key generated")
|
t.Fatal("empty private key generated")
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeConfig.NewKeys()
|
nodeConfig.NewKeys()
|
||||||
|
|
||||||
publicKey2, err := hex.DecodeString(nodeConfig.PublicKey)
|
publicKey2, err := hex.DecodeString(nodeConfig.PublicKey)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("can not decode generated public key")
|
t.Fatal("can not decode generated public key")
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Equal(publicKey2, publicKey1) {
|
if bytes.Equal(publicKey2, publicKey1) {
|
||||||
t.Fatal("same public key generated")
|
t.Fatal("same public key generated")
|
||||||
}
|
}
|
||||||
|
|
||||||
privateKey2, err := hex.DecodeString(nodeConfig.PrivateKey)
|
privateKey2, err := hex.DecodeString(nodeConfig.PrivateKey)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("can not decode generated private key")
|
t.Fatal("can not decode generated private key")
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Equal(privateKey2, privateKey1) {
|
if bytes.Equal(privateKey2, privateKey1) {
|
||||||
t.Fatal("same private key generated")
|
t.Fatal("same private key generated")
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
34
src/config/defaults.go
Normal file
34
src/config/defaults.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
var defaultConfig = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/config.defaultConfig=/path/to/config
|
||||||
|
var defaultAdminListen = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/config.defaultAdminListen=unix://path/to/sock'
|
||||||
|
|
||||||
|
// Defines which parameters are expected by default for configuration on a
|
||||||
|
// specific platform. These values are populated in the relevant defaults_*.go
|
||||||
|
// for the platform being targeted. They must be set.
|
||||||
|
type platformDefaultParameters struct {
|
||||||
|
// Admin socket
|
||||||
|
DefaultAdminListen string
|
||||||
|
|
||||||
|
// Configuration (used for yggdrasilctl)
|
||||||
|
DefaultConfigFile string
|
||||||
|
|
||||||
|
// Multicast interfaces
|
||||||
|
DefaultMulticastInterfaces []MulticastInterfaceConfig
|
||||||
|
|
||||||
|
// TUN
|
||||||
|
MaximumIfMTU uint64
|
||||||
|
DefaultIfMTU uint64
|
||||||
|
DefaultIfName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDefaults() platformDefaultParameters {
|
||||||
|
defaults := getDefaults()
|
||||||
|
if defaultConfig != "" {
|
||||||
|
defaults.DefaultConfigFile = defaultConfig
|
||||||
|
}
|
||||||
|
if defaultAdminListen != "" {
|
||||||
|
defaults.DefaultAdminListen = defaultAdminListen
|
||||||
|
}
|
||||||
|
return defaults
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
//go:build darwin
|
//go:build darwin
|
||||||
// +build darwin
|
// +build darwin
|
||||||
|
|
||||||
package defaults
|
package config
|
||||||
|
|
||||||
// Sane defaults for the macOS/Darwin platform. The "default" options may be
|
// Sane defaults for the macOS/Darwin platform. The "default" options may be
|
||||||
// may be replaced by the running configuration.
|
// may be replaced by the running configuration.
|
||||||
|
@ -17,6 +17,7 @@ func getDefaults() platformDefaultParameters {
|
||||||
DefaultMulticastInterfaces: []MulticastInterfaceConfig{
|
DefaultMulticastInterfaces: []MulticastInterfaceConfig{
|
||||||
{Regex: "en.*", Beacon: true, Listen: true},
|
{Regex: "en.*", Beacon: true, Listen: true},
|
||||||
{Regex: "bridge.*", Beacon: true, Listen: true},
|
{Regex: "bridge.*", Beacon: true, Listen: true},
|
||||||
|
{Regex: "awdl0", Beacon: false, Listen: false},
|
||||||
},
|
},
|
||||||
|
|
||||||
// TUN
|
// TUN
|
|
@ -1,7 +1,7 @@
|
||||||
//go:build freebsd
|
//go:build freebsd
|
||||||
// +build freebsd
|
// +build freebsd
|
||||||
|
|
||||||
package defaults
|
package config
|
||||||
|
|
||||||
// Sane defaults for the BSD platforms. The "default" options may be
|
// Sane defaults for the BSD platforms. The "default" options may be
|
||||||
// may be replaced by the running configuration.
|
// may be replaced by the running configuration.
|
|
@ -1,7 +1,7 @@
|
||||||
//go:build linux
|
//go:build linux
|
||||||
// +build linux
|
// +build linux
|
||||||
|
|
||||||
package defaults
|
package config
|
||||||
|
|
||||||
// Sane defaults for the Linux platform. The "default" options may be
|
// Sane defaults for the Linux platform. The "default" options may be
|
||||||
// may be replaced by the running configuration.
|
// may be replaced by the running configuration.
|
|
@ -1,7 +1,7 @@
|
||||||
//go:build openbsd
|
//go:build openbsd
|
||||||
// +build openbsd
|
// +build openbsd
|
||||||
|
|
||||||
package defaults
|
package config
|
||||||
|
|
||||||
// Sane defaults for the BSD platforms. The "default" options may be
|
// Sane defaults for the BSD platforms. The "default" options may be
|
||||||
// may be replaced by the running configuration.
|
// may be replaced by the running configuration.
|
|
@ -1,7 +1,7 @@
|
||||||
//go:build !linux && !darwin && !windows && !openbsd && !freebsd
|
//go:build !linux && !darwin && !windows && !openbsd && !freebsd
|
||||||
// +build !linux,!darwin,!windows,!openbsd,!freebsd
|
// +build !linux,!darwin,!windows,!openbsd,!freebsd
|
||||||
|
|
||||||
package defaults
|
package config
|
||||||
|
|
||||||
// Sane defaults for the other platforms. The "default" options may be
|
// Sane defaults for the other platforms. The "default" options may be
|
||||||
// may be replaced by the running configuration.
|
// may be replaced by the running configuration.
|
|
@ -1,7 +1,7 @@
|
||||||
//go:build windows
|
//go:build windows
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package defaults
|
package config
|
||||||
|
|
||||||
// Sane defaults for the Windows platform. The "default" options may be
|
// Sane defaults for the Windows platform. The "default" options may be
|
||||||
// may be replaced by the running configuration.
|
// may be replaced by the running configuration.
|
200
src/core/api.go
200
src/core/api.go
|
@ -3,43 +3,54 @@ package core
|
||||||
import (
|
import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Arceliar/phony"
|
"github.com/Arceliar/phony"
|
||||||
|
|
||||||
|
"github.com/Arceliar/ironwood/network"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SelfInfo struct {
|
type SelfInfo struct {
|
||||||
Key ed25519.PublicKey
|
Key ed25519.PublicKey
|
||||||
Root ed25519.PublicKey
|
RoutingEntries uint64
|
||||||
Coords []uint64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PeerInfo struct {
|
type PeerInfo struct {
|
||||||
Key ed25519.PublicKey
|
URI string
|
||||||
Root ed25519.PublicKey
|
Up bool
|
||||||
Coords []uint64
|
Inbound bool
|
||||||
Port uint64
|
LastError error
|
||||||
Priority uint8
|
LastErrorTime time.Time
|
||||||
Remote string
|
Key ed25519.PublicKey
|
||||||
RXBytes uint64
|
Root ed25519.PublicKey
|
||||||
TXBytes uint64
|
Coords []uint64
|
||||||
Uptime time.Duration
|
Port uint64
|
||||||
|
Priority uint8
|
||||||
|
Cost uint64
|
||||||
|
RXBytes uint64
|
||||||
|
TXBytes uint64
|
||||||
|
RXRate uint64
|
||||||
|
TXRate uint64
|
||||||
|
Uptime time.Duration
|
||||||
|
Latency time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
type DHTEntryInfo struct {
|
type TreeEntryInfo struct {
|
||||||
Key ed25519.PublicKey
|
Key ed25519.PublicKey
|
||||||
Port uint64
|
Parent ed25519.PublicKey
|
||||||
Rest uint64
|
Sequence uint64
|
||||||
|
//Port uint64
|
||||||
|
//Rest uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type PathEntryInfo struct {
|
type PathEntryInfo struct {
|
||||||
Key ed25519.PublicKey
|
Key ed25519.PublicKey
|
||||||
Path []uint64
|
Path []uint64
|
||||||
|
Sequence uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionInfo struct {
|
type SessionInfo struct {
|
||||||
|
@ -53,55 +64,63 @@ func (c *Core) GetSelf() SelfInfo {
|
||||||
var self SelfInfo
|
var self SelfInfo
|
||||||
s := c.PacketConn.PacketConn.Debug.GetSelf()
|
s := c.PacketConn.PacketConn.Debug.GetSelf()
|
||||||
self.Key = s.Key
|
self.Key = s.Key
|
||||||
self.Root = s.Root
|
self.RoutingEntries = s.RoutingEntries
|
||||||
self.Coords = s.Coords
|
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Core) GetPeers() []PeerInfo {
|
func (c *Core) GetPeers() []PeerInfo {
|
||||||
var peers []PeerInfo
|
peers := []PeerInfo{}
|
||||||
names := make(map[net.Conn]string)
|
conns := map[net.Conn]network.DebugPeerInfo{}
|
||||||
|
iwpeers := c.PacketConn.PacketConn.Debug.GetPeers()
|
||||||
|
for _, p := range iwpeers {
|
||||||
|
conns[p.Conn] = p
|
||||||
|
}
|
||||||
|
|
||||||
phony.Block(&c.links, func() {
|
phony.Block(&c.links, func() {
|
||||||
for _, info := range c.links._links {
|
for info, state := range c.links._links {
|
||||||
if info == nil {
|
var peerinfo PeerInfo
|
||||||
continue
|
var conn net.Conn
|
||||||
|
peerinfo.URI = info.uri
|
||||||
|
peerinfo.LastError = state._err
|
||||||
|
peerinfo.LastErrorTime = state._errtime
|
||||||
|
if c := state._conn; c != nil {
|
||||||
|
conn = c
|
||||||
|
peerinfo.Up = true
|
||||||
|
peerinfo.Inbound = state.linkType == linkTypeIncoming
|
||||||
|
peerinfo.RXBytes = atomic.LoadUint64(&c.rx)
|
||||||
|
peerinfo.TXBytes = atomic.LoadUint64(&c.tx)
|
||||||
|
peerinfo.RXRate = atomic.LoadUint64(&c.rxrate)
|
||||||
|
peerinfo.TXRate = atomic.LoadUint64(&c.txrate)
|
||||||
|
peerinfo.Uptime = time.Since(c.up)
|
||||||
}
|
}
|
||||||
names[info.conn] = info.lname
|
if p, ok := conns[conn]; ok {
|
||||||
|
peerinfo.Key = p.Key
|
||||||
|
peerinfo.Root = p.Root
|
||||||
|
peerinfo.Port = p.Port
|
||||||
|
peerinfo.Priority = p.Priority
|
||||||
|
peerinfo.Latency = p.Latency
|
||||||
|
peerinfo.Cost = p.Cost
|
||||||
|
}
|
||||||
|
peers = append(peers, peerinfo)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
ps := c.PacketConn.PacketConn.Debug.GetPeers()
|
|
||||||
for _, p := range ps {
|
|
||||||
var info PeerInfo
|
|
||||||
info.Key = p.Key
|
|
||||||
info.Root = p.Root
|
|
||||||
info.Coords = p.Coords
|
|
||||||
info.Port = p.Port
|
|
||||||
info.Priority = p.Priority
|
|
||||||
info.Remote = p.Conn.RemoteAddr().String()
|
|
||||||
if name := names[p.Conn]; name != "" {
|
|
||||||
info.Remote = name
|
|
||||||
}
|
|
||||||
if linkconn, ok := p.Conn.(*linkConn); ok {
|
|
||||||
info.RXBytes = atomic.LoadUint64(&linkconn.rx)
|
|
||||||
info.TXBytes = atomic.LoadUint64(&linkconn.tx)
|
|
||||||
info.Uptime = time.Since(linkconn.up)
|
|
||||||
}
|
|
||||||
peers = append(peers, info)
|
|
||||||
}
|
|
||||||
return peers
|
return peers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Core) GetDHT() []DHTEntryInfo {
|
func (c *Core) GetTree() []TreeEntryInfo {
|
||||||
var dhts []DHTEntryInfo
|
var trees []TreeEntryInfo
|
||||||
ds := c.PacketConn.PacketConn.Debug.GetDHT()
|
ts := c.PacketConn.PacketConn.Debug.GetTree()
|
||||||
for _, d := range ds {
|
for _, t := range ts {
|
||||||
var info DHTEntryInfo
|
var info TreeEntryInfo
|
||||||
info.Key = d.Key
|
info.Key = t.Key
|
||||||
info.Port = d.Port
|
info.Parent = t.Parent
|
||||||
info.Rest = d.Rest
|
info.Sequence = t.Sequence
|
||||||
dhts = append(dhts, info)
|
//info.Port = d.Port
|
||||||
|
//info.Rest = d.Rest
|
||||||
|
trees = append(trees, info)
|
||||||
}
|
}
|
||||||
return dhts
|
return trees
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Core) GetPaths() []PathEntryInfo {
|
func (c *Core) GetPaths() []PathEntryInfo {
|
||||||
|
@ -110,6 +129,7 @@ func (c *Core) GetPaths() []PathEntryInfo {
|
||||||
for _, p := range ps {
|
for _, p := range ps {
|
||||||
var info PathEntryInfo
|
var info PathEntryInfo
|
||||||
info.Key = p.Key
|
info.Key = p.Key
|
||||||
|
info.Sequence = p.Sequence
|
||||||
info.Path = p.Path
|
info.Path = p.Path
|
||||||
paths = append(paths, info)
|
paths = append(paths, info)
|
||||||
}
|
}
|
||||||
|
@ -134,16 +154,14 @@ func (c *Core) GetSessions() []SessionInfo {
|
||||||
// parsed from a string of the form e.g. "tcp://a.b.c.d:e". In the case of a
|
// parsed from a string of the form e.g. "tcp://a.b.c.d:e". In the case of a
|
||||||
// link-local address, the interface should be provided as the second argument.
|
// link-local address, the interface should be provided as the second argument.
|
||||||
func (c *Core) Listen(u *url.URL, sintf string) (*Listener, error) {
|
func (c *Core) Listen(u *url.URL, sintf string) (*Listener, error) {
|
||||||
switch u.Scheme {
|
return c.links.listen(u, sintf, false)
|
||||||
case "tcp":
|
}
|
||||||
return c.links.tcp.listen(u, sintf)
|
|
||||||
case "tls":
|
// ListenLocal starts a listener, like the Listen function, but is used for
|
||||||
return c.links.tls.listen(u, sintf)
|
// more trustworthy situations where you want to ignore AllowedPublicKeys, i.e.
|
||||||
case "unix":
|
// with multicast listeners.
|
||||||
return c.links.unix.listen(u, sintf)
|
func (c *Core) ListenLocal(u *url.URL, sintf string) (*Listener, error) {
|
||||||
default:
|
return c.links.listen(u, sintf, true)
|
||||||
return nil, fmt.Errorf("unrecognised scheme %q", u.Scheme)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address gets the IPv6 address of the Yggdrasil node. This is always a /128
|
// Address gets the IPv6 address of the Yggdrasil node. This is always a /128
|
||||||
|
@ -182,49 +200,14 @@ func (c *Core) SetLogger(log Logger) {
|
||||||
//
|
//
|
||||||
// This adds the peer to the peer list, so that they will be called again if the
|
// This adds the peer to the peer list, so that they will be called again if the
|
||||||
// connection drops.
|
// connection drops.
|
||||||
func (c *Core) AddPeer(uri string, sourceInterface string) error {
|
func (c *Core) AddPeer(u *url.URL, sintf string) error {
|
||||||
var known bool
|
return c.links.add(u, sintf, linkTypePersistent)
|
||||||
phony.Block(c, func() {
|
|
||||||
_, known = c.config._peers[Peer{uri, sourceInterface}]
|
|
||||||
})
|
|
||||||
if known {
|
|
||||||
return fmt.Errorf("peer already configured")
|
|
||||||
}
|
|
||||||
u, err := url.Parse(uri)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info, err := c.links.call(u, sourceInterface, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
phony.Block(c, func() {
|
|
||||||
c.config._peers[Peer{uri, sourceInterface}] = &info
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemovePeer removes a peer. The peer should be specified in URI format, see AddPeer.
|
// RemovePeer removes a peer. The peer should be specified in URI format, see AddPeer.
|
||||||
// The peer is not disconnected immediately.
|
// The peer is not disconnected immediately.
|
||||||
func (c *Core) RemovePeer(uri string, sourceInterface string) error {
|
func (c *Core) RemovePeer(u *url.URL, sintf string) error {
|
||||||
var err error
|
return c.links.remove(u, sintf, linkTypePersistent)
|
||||||
phony.Block(c, func() {
|
|
||||||
peer := Peer{uri, sourceInterface}
|
|
||||||
linkInfo, ok := c.config._peers[peer]
|
|
||||||
if !ok {
|
|
||||||
err = fmt.Errorf("peer not configured")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ok && linkInfo != nil {
|
|
||||||
c.links.Act(nil, func() {
|
|
||||||
if link := c.links._links[*linkInfo]; link != nil {
|
|
||||||
_ = link.close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
delete(c.config._peers, peer)
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallPeer calls a peer once. This should be specified in the peer URI format,
|
// CallPeer calls a peer once. This should be specified in the peer URI format,
|
||||||
|
@ -236,8 +219,7 @@ func (c *Core) RemovePeer(uri string, sourceInterface string) error {
|
||||||
// This does not add the peer to the peer list, so if the connection drops, the
|
// This does not add the peer to the peer list, so if the connection drops, the
|
||||||
// peer will not be called again automatically.
|
// peer will not be called again automatically.
|
||||||
func (c *Core) CallPeer(u *url.URL, sintf string) error {
|
func (c *Core) CallPeer(u *url.URL, sintf string) error {
|
||||||
_, err := c.links.call(u, sintf, nil)
|
return c.links.add(u, sintf, linkTypeEphemeral)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Core) PublicKey() ed25519.PublicKey {
|
func (c *Core) PublicKey() ed25519.PublicKey {
|
||||||
|
@ -274,8 +256,8 @@ func (c *Core) SetAdmin(a AddHandler) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := a.AddHandler(
|
if err := a.AddHandler(
|
||||||
"debug_remoteGetDHT", "Debug use only", []string{"key"},
|
"debug_remoteGetTree", "Debug use only", []string{"key"},
|
||||||
c.proto.getDHTHandler,
|
c.proto.getTreeHandler,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
145
src/core/core.go
145
src/core/core.go
|
@ -3,6 +3,7 @@ package core
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
@ -10,10 +11,12 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
iwe "github.com/Arceliar/ironwood/encrypted"
|
iwe "github.com/Arceliar/ironwood/encrypted"
|
||||||
|
iwn "github.com/Arceliar/ironwood/network"
|
||||||
iwt "github.com/Arceliar/ironwood/types"
|
iwt "github.com/Arceliar/ironwood/types"
|
||||||
"github.com/Arceliar/phony"
|
"github.com/Arceliar/phony"
|
||||||
"github.com/gologme/log"
|
"github.com/gologme/log"
|
||||||
|
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/version"
|
"github.com/yggdrasil-network/yggdrasil-go/src/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,49 +37,88 @@ type Core struct {
|
||||||
log Logger
|
log Logger
|
||||||
addPeerTimer *time.Timer
|
addPeerTimer *time.Timer
|
||||||
config struct {
|
config struct {
|
||||||
_peers map[Peer]*linkInfo // configurable after startup
|
tls *tls.Config // immutable after startup
|
||||||
|
//_peers map[Peer]*linkInfo // configurable after startup
|
||||||
_listeners map[ListenAddress]struct{} // configurable after startup
|
_listeners map[ListenAddress]struct{} // configurable after startup
|
||||||
|
peerFilter func(ip net.IP) bool // immutable after startup
|
||||||
nodeinfo NodeInfo // immutable after startup
|
nodeinfo NodeInfo // immutable after startup
|
||||||
nodeinfoPrivacy NodeInfoPrivacy // immutable after startup
|
nodeinfoPrivacy NodeInfoPrivacy // immutable after startup
|
||||||
_allowedPublicKeys map[[32]byte]struct{} // configurable after startup
|
_allowedPublicKeys map[[32]byte]struct{} // configurable after startup
|
||||||
}
|
}
|
||||||
|
pathNotify func(ed25519.PublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(secret ed25519.PrivateKey, logger Logger, opts ...SetupOption) (*Core, error) {
|
func New(cert *tls.Certificate, logger Logger, opts ...SetupOption) (*Core, error) {
|
||||||
c := &Core{
|
c := &Core{
|
||||||
log: logger,
|
log: logger,
|
||||||
}
|
}
|
||||||
|
c.ctx, c.cancel = context.WithCancel(context.Background())
|
||||||
|
if c.log == nil {
|
||||||
|
c.log = log.New(io.Discard, "", 0)
|
||||||
|
}
|
||||||
|
|
||||||
if name := version.BuildName(); name != "unknown" {
|
if name := version.BuildName(); name != "unknown" {
|
||||||
c.log.Infoln("Build name:", name)
|
c.log.Infoln("Build name:", name)
|
||||||
}
|
}
|
||||||
if version := version.BuildVersion(); version != "unknown" {
|
if version := version.BuildVersion(); version != "unknown" {
|
||||||
c.log.Infoln("Build version:", version)
|
c.log.Infoln("Build version:", version)
|
||||||
}
|
}
|
||||||
c.ctx, c.cancel = context.WithCancel(context.Background())
|
|
||||||
// Take a copy of the private key so that it is in our own memory space.
|
|
||||||
if len(secret) != ed25519.PrivateKeySize {
|
|
||||||
return nil, fmt.Errorf("private key is incorrect length")
|
|
||||||
}
|
|
||||||
c.secret = make(ed25519.PrivateKey, ed25519.PrivateKeySize)
|
|
||||||
copy(c.secret, secret)
|
|
||||||
c.public = secret.Public().(ed25519.PublicKey)
|
|
||||||
var err error
|
var err error
|
||||||
if c.PacketConn, err = iwe.NewPacketConn(c.secret); err != nil {
|
|
||||||
return nil, fmt.Errorf("error creating encryption: %w", err)
|
|
||||||
}
|
|
||||||
c.config._peers = map[Peer]*linkInfo{}
|
|
||||||
c.config._listeners = map[ListenAddress]struct{}{}
|
c.config._listeners = map[ListenAddress]struct{}{}
|
||||||
c.config._allowedPublicKeys = map[[32]byte]struct{}{}
|
c.config._allowedPublicKeys = map[[32]byte]struct{}{}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
c._applyOption(opt)
|
switch opt.(type) {
|
||||||
|
case Peer, ListenAddress:
|
||||||
|
// We can't do peers yet as the links aren't set up.
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
if err = c._applyOption(opt); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to apply configuration option %T: %w", opt, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if c.log == nil {
|
if cert == nil || cert.PrivateKey == nil {
|
||||||
c.log = log.New(io.Discard, "", 0)
|
return nil, fmt.Errorf("no private key supplied")
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
if c.secret, ok = cert.PrivateKey.(ed25519.PrivateKey); !ok {
|
||||||
|
return nil, fmt.Errorf("private key must be ed25519")
|
||||||
|
}
|
||||||
|
if len(c.secret) != ed25519.PrivateKeySize {
|
||||||
|
return nil, fmt.Errorf("private key is incorrect length")
|
||||||
|
}
|
||||||
|
c.public = c.secret.Public().(ed25519.PublicKey)
|
||||||
|
|
||||||
|
if c.config.tls, err = c.generateTLSConfig(cert); err != nil {
|
||||||
|
return nil, fmt.Errorf("error generating TLS config: %w", err)
|
||||||
|
}
|
||||||
|
keyXform := func(key ed25519.PublicKey) ed25519.PublicKey {
|
||||||
|
return address.SubnetForKey(key).GetKey()
|
||||||
|
}
|
||||||
|
if c.PacketConn, err = iwe.NewPacketConn(
|
||||||
|
c.secret,
|
||||||
|
iwn.WithBloomTransform(keyXform),
|
||||||
|
iwn.WithPeerMaxMessageSize(65535*2),
|
||||||
|
iwn.WithPathNotify(c.doPathNotify),
|
||||||
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating encryption: %w", err)
|
||||||
}
|
}
|
||||||
c.proto.init(c)
|
c.proto.init(c)
|
||||||
if err := c.links.init(c); err != nil {
|
if err := c.links.init(c); err != nil {
|
||||||
return nil, fmt.Errorf("error initialising links: %w", err)
|
return nil, fmt.Errorf("error initialising links: %w", err)
|
||||||
}
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
switch opt.(type) {
|
||||||
|
case Peer, ListenAddress:
|
||||||
|
// Now do the peers and listeners.
|
||||||
|
if err = c._applyOption(opt); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to apply configuration option %T: %w", opt, err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := c.proto.nodeinfo.setNodeInfo(c.config.nodeinfo, bool(c.config.nodeinfoPrivacy)); err != nil {
|
if err := c.proto.nodeinfo.setNodeInfo(c.config.nodeinfo, bool(c.config.nodeinfoPrivacy)); err != nil {
|
||||||
return nil, fmt.Errorf("error setting node info: %w", err)
|
return nil, fmt.Errorf("error setting node info: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -86,46 +128,22 @@ func New(secret ed25519.PrivateKey, logger Logger, opts ...SetupOption) (*Core,
|
||||||
c.log.Errorf("Invalid listener URI %q specified, ignoring\n", listenaddr)
|
c.log.Errorf("Invalid listener URI %q specified, ignoring\n", listenaddr)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, err = c.links.listen(u, ""); err != nil {
|
if _, err = c.links.listen(u, "", false); err != nil {
|
||||||
c.log.Errorf("Failed to start listener %q: %s\n", listenaddr, err)
|
c.log.Errorf("Failed to start listener %q: %s\n", listenaddr, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.Act(nil, c._addPeerLoop)
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any static peers were provided in the configuration above then we should
|
|
||||||
// configure them. The loop ensures that disconnected peers will eventually
|
|
||||||
// be reconnected with.
|
|
||||||
func (c *Core) _addPeerLoop() {
|
|
||||||
select {
|
|
||||||
case <-c.ctx.Done():
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
// Add peers from the Peers section
|
|
||||||
for peer := range c.config._peers {
|
|
||||||
go func(peer string, intf string) {
|
|
||||||
u, err := url.Parse(peer)
|
|
||||||
if err != nil {
|
|
||||||
c.log.Errorln("Failed to parse peer url:", peer, err)
|
|
||||||
}
|
|
||||||
if err := c.CallPeer(u, intf); err != nil {
|
|
||||||
c.log.Errorln("Failed to add peer:", err)
|
|
||||||
}
|
|
||||||
}(peer.URI, peer.SourceInterface) // TODO: this should be acted and not in a goroutine?
|
|
||||||
}
|
|
||||||
|
|
||||||
c.addPeerTimer = time.AfterFunc(time.Minute, func() {
|
|
||||||
c.Act(nil, c._addPeerLoop)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Core) RetryPeersNow() {
|
func (c *Core) RetryPeersNow() {
|
||||||
if c.addPeerTimer != nil && !c.addPeerTimer.Stop() {
|
phony.Block(&c.links, func() {
|
||||||
<-c.addPeerTimer.C
|
for _, l := range c.links._links {
|
||||||
}
|
select {
|
||||||
c.Act(nil, c._addPeerLoop)
|
case l.kick <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop shuts down the Yggdrasil node.
|
// Stop shuts down the Yggdrasil node.
|
||||||
|
@ -151,11 +169,16 @@ func (c *Core) _close() error {
|
||||||
|
|
||||||
func (c *Core) MTU() uint64 {
|
func (c *Core) MTU() uint64 {
|
||||||
const sessionTypeOverhead = 1
|
const sessionTypeOverhead = 1
|
||||||
return c.PacketConn.MTU() - sessionTypeOverhead
|
MTU := c.PacketConn.MTU() - sessionTypeOverhead
|
||||||
|
if MTU > 65535 {
|
||||||
|
MTU = 65535
|
||||||
|
}
|
||||||
|
return MTU
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Core) ReadFrom(p []byte) (n int, from net.Addr, err error) {
|
func (c *Core) ReadFrom(p []byte) (n int, from net.Addr, err error) {
|
||||||
buf := make([]byte, c.PacketConn.MTU(), 65535)
|
buf := allocBytes(int(c.PacketConn.MTU()))
|
||||||
|
defer freeBytes(buf)
|
||||||
for {
|
for {
|
||||||
bs := buf
|
bs := buf
|
||||||
n, from, err = c.PacketConn.ReadFrom(bs)
|
n, from, err = c.PacketConn.ReadFrom(bs)
|
||||||
|
@ -189,7 +212,8 @@ func (c *Core) ReadFrom(p []byte) (n int, from net.Addr, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
buf := make([]byte, 0, 65535)
|
buf := allocBytes(0)
|
||||||
|
defer func() { freeBytes(buf) }()
|
||||||
buf = append(buf, typeSessionTraffic)
|
buf = append(buf, typeSessionTraffic)
|
||||||
buf = append(buf, p...)
|
buf = append(buf, p...)
|
||||||
n, err = c.PacketConn.WriteTo(buf, addr)
|
n, err = c.PacketConn.WriteTo(buf, addr)
|
||||||
|
@ -199,6 +223,20 @@ func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Core) doPathNotify(key ed25519.PublicKey) {
|
||||||
|
c.Act(nil, func() {
|
||||||
|
if c.pathNotify != nil {
|
||||||
|
c.pathNotify(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) SetPathNotify(notify func(ed25519.PublicKey)) {
|
||||||
|
c.Act(nil, func() {
|
||||||
|
c.pathNotify = notify
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
Printf(string, ...interface{})
|
Printf(string, ...interface{})
|
||||||
Println(...interface{})
|
Println(...interface{})
|
||||||
|
@ -210,4 +248,5 @@ type Logger interface {
|
||||||
Errorln(...interface{})
|
Errorln(...interface{})
|
||||||
Debugf(string, ...interface{})
|
Debugf(string, ...interface{})
|
||||||
Debugln(...interface{})
|
Debugln(...interface{})
|
||||||
|
Traceln(...interface{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,14 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ed25519"
|
"crypto/rand"
|
||||||
"math/rand"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gologme/log"
|
"github.com/gologme/log"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetLoggerWithPrefix creates a new logger instance with prefix.
|
// GetLoggerWithPrefix creates a new logger instance with prefix.
|
||||||
|
@ -25,33 +25,65 @@ func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger {
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func require_NoError(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func require_Equal[T comparable](t *testing.T, a, b T) {
|
||||||
|
t.Helper()
|
||||||
|
if a != b {
|
||||||
|
t.Fatalf("%v != %v", a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func require_True(t *testing.T, a bool) {
|
||||||
|
t.Helper()
|
||||||
|
if !a {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CreateAndConnectTwo creates two nodes. nodeB connects to nodeA.
|
// CreateAndConnectTwo creates two nodes. nodeB connects to nodeA.
|
||||||
// Verbosity flag is passed to logger.
|
// Verbosity flag is passed to logger.
|
||||||
func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) {
|
func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) {
|
||||||
var err error
|
var err error
|
||||||
var skA, skB ed25519.PrivateKey
|
|
||||||
if _, skA, err = ed25519.GenerateKey(nil); err != nil {
|
cfgA, cfgB := config.GenerateConfig(), config.GenerateConfig()
|
||||||
|
if err = cfgA.GenerateSelfSignedCertificate(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if _, skB, err = ed25519.GenerateKey(nil); err != nil {
|
if err = cfgB.GenerateSelfSignedCertificate(); err != nil {
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
logger := GetLoggerWithPrefix("", false)
|
|
||||||
if nodeA, err = New(skA, logger, ListenAddress("tcp://127.0.0.1:0")); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if nodeB, err = New(skB, logger, ListenAddress("tcp://127.0.0.1:0")); err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := url.Parse("tcp://" + nodeA.links.tcp.getAddr().String())
|
logger := GetLoggerWithPrefix("", false)
|
||||||
|
logger.EnableLevel("debug")
|
||||||
|
|
||||||
|
if nodeA, err = New(cfgA.Certificate, logger); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if nodeB, err = New(cfgB.Certificate, logger); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeAListenURL, err := url.Parse("tcp://localhost:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
err = nodeB.CallPeer(u, "")
|
nodeAListener, err := nodeA.Listen(nodeAListenURL, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
nodeAURL, err := url.Parse("tcp://" + nodeAListener.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = nodeB.CallPeer(nodeAURL, ""); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
@ -70,7 +102,13 @@ func WaitConnected(nodeA, nodeB *Core) bool {
|
||||||
// It may take up to 3 seconds, but let's wait 5.
|
// It may take up to 3 seconds, but let's wait 5.
|
||||||
for i := 0; i < 50; i++ {
|
for i := 0; i < 50; i++ {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
if len(nodeA.GetPeers()) > 0 && len(nodeB.GetPeers()) > 0 {
|
/*
|
||||||
|
if len(nodeA.GetPeers()) > 0 && len(nodeB.GetPeers()) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if len(nodeA.GetTree()) > 1 && len(nodeB.GetTree()) > 1 {
|
||||||
|
time.Sleep(3 * time.Second) // FIXME hack, there's still stuff happening internally
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,7 +167,7 @@ func TestCore_Start_Transfer(t *testing.T) {
|
||||||
|
|
||||||
// Send
|
// Send
|
||||||
msg := make([]byte, msgLen)
|
msg := make([]byte, msgLen)
|
||||||
rand.Read(msg[40:])
|
_, _ = rand.Read(msg[40:])
|
||||||
msg[0] = 0x60
|
msg[0] = 0x60
|
||||||
copy(msg[8:24], nodeB.Address())
|
copy(msg[8:24], nodeB.Address())
|
||||||
copy(msg[24:40], nodeA.Address())
|
copy(msg[24:40], nodeA.Address())
|
||||||
|
@ -161,7 +199,7 @@ func BenchmarkCore_Start_Transfer(b *testing.B) {
|
||||||
|
|
||||||
// Send
|
// Send
|
||||||
msg := make([]byte, msgLen)
|
msg := make([]byte, msgLen)
|
||||||
rand.Read(msg[40:])
|
_, _ = rand.Read(msg[40:])
|
||||||
msg[0] = 0x60
|
msg[0] = 0x60
|
||||||
copy(msg[8:24], nodeB.Address())
|
copy(msg[8:24], nodeB.Address())
|
||||||
copy(msg[24:40], nodeA.Address())
|
copy(msg[24:40], nodeA.Address())
|
||||||
|
@ -184,3 +222,69 @@ func BenchmarkCore_Start_Transfer(b *testing.B) {
|
||||||
}
|
}
|
||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAllowedPublicKeys(t *testing.T) {
|
||||||
|
logger := GetLoggerWithPrefix("", false)
|
||||||
|
cfgA, cfgB := config.GenerateConfig(), config.GenerateConfig()
|
||||||
|
require_NoError(t, cfgA.GenerateSelfSignedCertificate())
|
||||||
|
require_NoError(t, cfgB.GenerateSelfSignedCertificate())
|
||||||
|
|
||||||
|
nodeA, err := New(cfgA.Certificate, logger, AllowedPublicKey("abcdef"))
|
||||||
|
require_NoError(t, err)
|
||||||
|
defer nodeA.Stop()
|
||||||
|
|
||||||
|
nodeB, err := New(cfgB.Certificate, logger)
|
||||||
|
require_NoError(t, err)
|
||||||
|
defer nodeB.Stop()
|
||||||
|
|
||||||
|
u, err := url.Parse("tcp://localhost:0")
|
||||||
|
require_NoError(t, err)
|
||||||
|
|
||||||
|
l, err := nodeA.Listen(u, "")
|
||||||
|
require_NoError(t, err)
|
||||||
|
|
||||||
|
u, err = url.Parse("tcp://" + l.Addr().String())
|
||||||
|
require_NoError(t, err)
|
||||||
|
|
||||||
|
require_NoError(t, nodeB.AddPeer(u, ""))
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
peers := nodeB.GetPeers()
|
||||||
|
require_Equal(t, len(peers), 1)
|
||||||
|
require_True(t, !peers[0].Up)
|
||||||
|
require_True(t, peers[0].LastError != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllowedPublicKeysLocal(t *testing.T) {
|
||||||
|
logger := GetLoggerWithPrefix("", false)
|
||||||
|
cfgA, cfgB := config.GenerateConfig(), config.GenerateConfig()
|
||||||
|
require_NoError(t, cfgA.GenerateSelfSignedCertificate())
|
||||||
|
require_NoError(t, cfgB.GenerateSelfSignedCertificate())
|
||||||
|
|
||||||
|
nodeA, err := New(cfgA.Certificate, logger, AllowedPublicKey("abcdef"))
|
||||||
|
require_NoError(t, err)
|
||||||
|
defer nodeA.Stop()
|
||||||
|
|
||||||
|
nodeB, err := New(cfgB.Certificate, logger)
|
||||||
|
require_NoError(t, err)
|
||||||
|
defer nodeB.Stop()
|
||||||
|
|
||||||
|
u, err := url.Parse("tcp://localhost:0")
|
||||||
|
require_NoError(t, err)
|
||||||
|
|
||||||
|
l, err := nodeA.ListenLocal(u, "")
|
||||||
|
require_NoError(t, err)
|
||||||
|
|
||||||
|
u, err = url.Parse("tcp://" + l.Addr().String())
|
||||||
|
require_NoError(t, err)
|
||||||
|
|
||||||
|
require_NoError(t, nodeB.AddPeer(u, ""))
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
peers := nodeB.GetPeers()
|
||||||
|
require_Equal(t, len(peers), 1)
|
||||||
|
require_True(t, peers[0].Up)
|
||||||
|
require_True(t, peers[0].LastError == nil)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
//go:build debug
|
|
||||||
// +build debug
|
|
||||||
|
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -8,28 +5,15 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/gologme/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Start the profiler in debug builds, if the required environment variable is set.
|
// Start the profiler if the required environment variable is set.
|
||||||
func init() {
|
func init() {
|
||||||
envVarName := "PPROFLISTEN"
|
envVarName := "PPROFLISTEN"
|
||||||
hostPort := os.Getenv(envVarName)
|
if hostPort := os.Getenv(envVarName); hostPort != "" {
|
||||||
switch {
|
|
||||||
case hostPort == "":
|
|
||||||
fmt.Fprintf(os.Stderr, "DEBUG: %s not set, profiler not started.\n", envVarName)
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(os.Stderr, "DEBUG: Starting pprof on %s\n", hostPort)
|
fmt.Fprintf(os.Stderr, "DEBUG: Starting pprof on %s\n", hostPort)
|
||||||
go func() { fmt.Println(http.ListenAndServe(hostPort, nil)) }()
|
go func() {
|
||||||
|
fmt.Fprintf(os.Stderr, "DEBUG: %s", http.ListenAndServe(hostPort, nil))
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts the function profiler. This is only supported when built with
|
|
||||||
// '-tags build'.
|
|
||||||
func StartProfiler(log *log.Logger) error {
|
|
||||||
runtime.SetBlockProfileRate(1)
|
|
||||||
go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
905
src/core/link.go
905
src/core/link.go
File diff suppressed because it is too large
Load diff
108
src/core/link_quic.go
Normal file
108
src/core/link_quic.go
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Arceliar/phony"
|
||||||
|
"github.com/quic-go/quic-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type linkQUIC struct {
|
||||||
|
phony.Inbox
|
||||||
|
*links
|
||||||
|
tlsconfig *tls.Config
|
||||||
|
quicconfig *quic.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
type linkQUICStream struct {
|
||||||
|
quic.Connection
|
||||||
|
quic.Stream
|
||||||
|
}
|
||||||
|
|
||||||
|
type linkQUICListener struct {
|
||||||
|
*quic.Listener
|
||||||
|
ch <-chan *linkQUICStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linkQUICListener) Accept() (net.Conn, error) {
|
||||||
|
qs := <-l.ch
|
||||||
|
if qs == nil {
|
||||||
|
return nil, context.Canceled
|
||||||
|
}
|
||||||
|
return qs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *links) newLinkQUIC() *linkQUIC {
|
||||||
|
lt := &linkQUIC{
|
||||||
|
links: l,
|
||||||
|
tlsconfig: l.core.config.tls.Clone(),
|
||||||
|
quicconfig: &quic.Config{
|
||||||
|
MaxIdleTimeout: time.Minute,
|
||||||
|
KeepAlivePeriod: time.Second * 20,
|
||||||
|
TokenStore: quic.NewLRUTokenStore(255, 255),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return lt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linkQUIC) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||||
|
tlsconfig := l.tlsconfig.Clone()
|
||||||
|
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
|
||||||
|
tlsconfig.ServerName = hostname
|
||||||
|
tlsconfig.MinVersion = tls.VersionTLS12
|
||||||
|
tlsconfig.MaxVersion = tls.VersionTLS13
|
||||||
|
hostport := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
|
||||||
|
qc, err := quic.DialAddr(ctx, hostport, l.tlsconfig, l.quicconfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
qs, err := qc.OpenStreamSync(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &linkQUICStream{
|
||||||
|
Connection: qc,
|
||||||
|
Stream: qs,
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linkQUIC) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
||||||
|
ql, err := quic.ListenAddr(url.Host, l.tlsconfig, l.quicconfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ch := make(chan *linkQUICStream)
|
||||||
|
lql := &linkQUICListener{
|
||||||
|
Listener: ql,
|
||||||
|
ch: ch,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
qc, err := ql.Accept(ctx)
|
||||||
|
switch err {
|
||||||
|
case context.Canceled, context.DeadlineExceeded:
|
||||||
|
ql.Close()
|
||||||
|
fallthrough
|
||||||
|
case quic.ErrServerClosed:
|
||||||
|
return
|
||||||
|
case nil:
|
||||||
|
qs, err := qc.AcceptStream(ctx)
|
||||||
|
if err != nil {
|
||||||
|
_ = qc.CloseWithError(1, fmt.Sprintf("stream error: %s", err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ch <- &linkQUICStream{
|
||||||
|
Connection: qc,
|
||||||
|
Stream: qs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return lql, nil
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -20,37 +22,46 @@ func (l *links) newLinkSOCKS() *linkSOCKS {
|
||||||
return lt
|
return lt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkSOCKS) dial(url *url.URL, options linkOptions) error {
|
func (l *linkSOCKS) dial(_ context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||||
info := linkInfoFor("socks", "", url.Path)
|
var proxyAuth *proxy.Auth
|
||||||
if l.links.isConnectedTo(info) {
|
if url.User != nil && url.User.Username() != "" {
|
||||||
return nil
|
proxyAuth = &proxy.Auth{
|
||||||
|
User: url.User.Username(),
|
||||||
|
}
|
||||||
|
proxyAuth.Password, _ = url.User.Password()
|
||||||
}
|
}
|
||||||
proxyAuth := &proxy.Auth{}
|
tlsconfig := l.tls.config.Clone()
|
||||||
proxyAuth.User = url.User.Username()
|
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
|
||||||
proxyAuth.Password, _ = url.User.Password()
|
hostport := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
|
||||||
dialer, err := proxy.SOCKS5("tcp", url.Host, proxyAuth, proxy.Direct)
|
dialer, err := l.tcp.dialerFor(&net.TCPAddr{
|
||||||
if err != nil {
|
IP: ip,
|
||||||
return fmt.Errorf("failed to configure proxy")
|
Port: port,
|
||||||
}
|
}, info.sintf)
|
||||||
pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/")
|
if err != nil {
|
||||||
conn, err := dialer.Dial("tcp", pathtokens[0])
|
return nil, err
|
||||||
if err != nil {
|
}
|
||||||
return err
|
proxy, err := proxy.SOCKS5("tcp", hostport, proxyAuth, dialer)
|
||||||
}
|
if err != nil {
|
||||||
dial := &linkDial{
|
return nil, err
|
||||||
url: url,
|
}
|
||||||
}
|
pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/")
|
||||||
return l.handler(dial, info, conn, options, false)
|
conn, err := proxy.Dial("tcp", pathtokens[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if url.Scheme == "sockstls" {
|
||||||
|
tlsconfig.ServerName = hostname
|
||||||
|
tlsconfig.MinVersion = tls.VersionTLS12
|
||||||
|
tlsconfig.MaxVersion = tls.VersionTLS13
|
||||||
|
if sni := options.tlsSNI; sni != "" {
|
||||||
|
tlsconfig.ServerName = sni
|
||||||
|
}
|
||||||
|
conn = tls.Client(conn, tlsconfig)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkSOCKS) handler(dial *linkDial, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error {
|
func (l *linkSOCKS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
||||||
return l.links.create(
|
return nil, fmt.Errorf("SOCKS listener not supported")
|
||||||
conn, // connection
|
|
||||||
dial, // connection URL
|
|
||||||
dial.url.String(), // connection name
|
|
||||||
info, // connection info
|
|
||||||
incoming, // not incoming
|
|
||||||
false, // not forced
|
|
||||||
options, // connection options
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Arceliar/phony"
|
"github.com/Arceliar/phony"
|
||||||
|
@ -14,117 +13,42 @@ import (
|
||||||
type linkTCP struct {
|
type linkTCP struct {
|
||||||
phony.Inbox
|
phony.Inbox
|
||||||
*links
|
*links
|
||||||
listener *net.ListenConfig
|
listenconfig *net.ListenConfig
|
||||||
_listeners map[*Listener]context.CancelFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *links) newLinkTCP() *linkTCP {
|
func (l *links) newLinkTCP() *linkTCP {
|
||||||
lt := &linkTCP{
|
lt := &linkTCP{
|
||||||
links: l,
|
links: l,
|
||||||
listener: &net.ListenConfig{
|
listenconfig: &net.ListenConfig{
|
||||||
KeepAlive: -1,
|
KeepAlive: -1,
|
||||||
},
|
},
|
||||||
_listeners: map[*Listener]context.CancelFunc{},
|
|
||||||
}
|
}
|
||||||
lt.listener.Control = lt.tcpContext
|
lt.listenconfig.Control = lt.tcpContext
|
||||||
return lt
|
return lt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkTCP) dial(url *url.URL, options linkOptions, sintf string) error {
|
func (l *linkTCP) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||||
addr, err := net.ResolveTCPAddr("tcp", url.Host)
|
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
|
||||||
if err != nil {
|
addr := &net.TCPAddr{
|
||||||
return err
|
IP: ip,
|
||||||
}
|
Port: port,
|
||||||
dialer, err := l.dialerFor(addr, sintf)
|
}
|
||||||
if err != nil {
|
dialer, err := l.tcp.dialerFor(addr, info.sintf)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
info := linkInfoFor("tcp", sintf, tcpIDFor(dialer.LocalAddr, addr))
|
}
|
||||||
if l.links.isConnectedTo(info) {
|
return dialer.DialContext(ctx, "tcp", addr.String())
|
||||||
return nil
|
})
|
||||||
}
|
|
||||||
conn, err := dialer.DialContext(l.core.ctx, "tcp", addr.String())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
name := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/")
|
|
||||||
dial := &linkDial{
|
|
||||||
url: url,
|
|
||||||
sintf: sintf,
|
|
||||||
}
|
|
||||||
return l.handler(dial, name, info, conn, options, false, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) {
|
func (l *linkTCP) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
|
||||||
ctx, cancel := context.WithCancel(l.core.ctx)
|
|
||||||
hostport := url.Host
|
hostport := url.Host
|
||||||
if sintf != "" {
|
if sintf != "" {
|
||||||
if host, port, err := net.SplitHostPort(hostport); err == nil {
|
if host, port, err := net.SplitHostPort(hostport); err == nil {
|
||||||
hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port)
|
hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listener, err := l.listener.Listen(ctx, "tcp", hostport)
|
return l.listenconfig.Listen(ctx, "tcp", hostport)
|
||||||
if err != nil {
|
|
||||||
cancel()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
entry := &Listener{
|
|
||||||
Listener: listener,
|
|
||||||
closed: make(chan struct{}),
|
|
||||||
}
|
|
||||||
phony.Block(l, func() {
|
|
||||||
l._listeners[entry] = cancel
|
|
||||||
})
|
|
||||||
l.core.log.Printf("TCP listener started on %s", listener.Addr())
|
|
||||||
go func() {
|
|
||||||
defer phony.Block(l, func() {
|
|
||||||
delete(l._listeners, entry)
|
|
||||||
})
|
|
||||||
for {
|
|
||||||
conn, err := listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
cancel()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
laddr := conn.LocalAddr().(*net.TCPAddr)
|
|
||||||
raddr := conn.RemoteAddr().(*net.TCPAddr)
|
|
||||||
name := fmt.Sprintf("tcp://%s", raddr)
|
|
||||||
info := linkInfoFor("tcp", sintf, tcpIDFor(laddr, raddr))
|
|
||||||
if err = l.handler(nil, name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil {
|
|
||||||
l.core.log.Errorln("Failed to create inbound link:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ = listener.Close()
|
|
||||||
close(entry.closed)
|
|
||||||
l.core.log.Printf("TCP listener stopped on %s", listener.Addr())
|
|
||||||
}()
|
|
||||||
return entry, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *linkTCP) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error {
|
|
||||||
return l.links.create(
|
|
||||||
conn, // connection
|
|
||||||
dial, // connection URL
|
|
||||||
name, // connection name
|
|
||||||
info, // connection info
|
|
||||||
incoming, // not incoming
|
|
||||||
force, // not forced
|
|
||||||
options, // connection options
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the address of the listener.
|
|
||||||
func (l *linkTCP) getAddr() *net.TCPAddr {
|
|
||||||
// TODO: Fix this, because this will currently only give a single address
|
|
||||||
// to multicast.go, which obviously is not great, but right now multicast.go
|
|
||||||
// doesn't have the ability to send more than one address in a packet either
|
|
||||||
var addr *net.TCPAddr
|
|
||||||
phony.Block(l, func() {
|
|
||||||
for listener := range l._listeners {
|
|
||||||
addr = listener.Addr().(*net.TCPAddr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return addr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkTCP) dialerFor(dst *net.TCPAddr, sintf string) (*net.Dialer, error) {
|
func (l *linkTCP) dialerFor(dst *net.TCPAddr, sintf string) (*net.Dialer, error) {
|
||||||
|
@ -185,16 +109,3 @@ func (l *linkTCP) dialerFor(dst *net.TCPAddr, sintf string) (*net.Dialer, error)
|
||||||
}
|
}
|
||||||
return dialer, nil
|
return dialer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func tcpIDFor(local net.Addr, remoteAddr *net.TCPAddr) string {
|
|
||||||
if localAddr, ok := local.(*net.TCPAddr); ok && localAddr.IP.Equal(remoteAddr.IP) {
|
|
||||||
// Nodes running on the same host — include both the IP and port.
|
|
||||||
return remoteAddr.String()
|
|
||||||
}
|
|
||||||
if remoteAddr.IP.IsLinkLocalUnicast() {
|
|
||||||
// Nodes discovered via multicast — include the IP only.
|
|
||||||
return remoteAddr.IP.String()
|
|
||||||
}
|
|
||||||
// Nodes connected remotely — include both the IP and port.
|
|
||||||
return remoteAddr.String()
|
|
||||||
}
|
|
||||||
|
|
|
@ -28,6 +28,6 @@ func (t *linkTCP) tcpContext(network, address string, c syscall.RawConn) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *linkTCP) getControl(sintf string) func(string, string, syscall.RawConn) error {
|
func (t *linkTCP) getControl(_ string) func(string, string, syscall.RawConn) error {
|
||||||
return t.tcpContext
|
return t.tcpContext
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,22 +12,6 @@ import (
|
||||||
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
|
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
|
||||||
|
|
||||||
func (t *linkTCP) tcpContext(network, address string, c syscall.RawConn) error {
|
func (t *linkTCP) tcpContext(network, address string, c syscall.RawConn) error {
|
||||||
var control error
|
|
||||||
var bbr error
|
|
||||||
|
|
||||||
control = c.Control(func(fd uintptr) {
|
|
||||||
bbr = unix.SetsockoptString(int(fd), unix.IPPROTO_TCP, unix.TCP_CONGESTION, "bbr")
|
|
||||||
})
|
|
||||||
|
|
||||||
// Log any errors
|
|
||||||
if bbr != nil {
|
|
||||||
t.links.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, SetsockoptString error:", bbr)
|
|
||||||
}
|
|
||||||
if control != nil {
|
|
||||||
t.links.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, Control error:", control)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return nil because errors here are not considered fatal for the connection, it just means congestion control is suboptimal
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Arceliar/phony"
|
"github.com/Arceliar/phony"
|
||||||
)
|
)
|
||||||
|
@ -22,10 +13,9 @@ import (
|
||||||
type linkTLS struct {
|
type linkTLS struct {
|
||||||
phony.Inbox
|
phony.Inbox
|
||||||
*links
|
*links
|
||||||
tcp *linkTCP
|
tcp *linkTCP
|
||||||
listener *net.ListenConfig
|
listener *net.ListenConfig
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
_listeners map[*Listener]context.CancelFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS {
|
func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS {
|
||||||
|
@ -36,49 +26,37 @@ func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS {
|
||||||
Control: tcp.tcpContext,
|
Control: tcp.tcpContext,
|
||||||
KeepAlive: -1,
|
KeepAlive: -1,
|
||||||
},
|
},
|
||||||
_listeners: map[*Listener]context.CancelFunc{},
|
config: l.core.config.tls.Clone(),
|
||||||
}
|
|
||||||
var err error
|
|
||||||
lt.config, err = lt.generateConfig()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
return lt
|
return lt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkTLS) dial(url *url.URL, options linkOptions, sintf, sni string) error {
|
func (l *linkTLS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||||
addr, err := net.ResolveTCPAddr("tcp", url.Host)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dialer, err := l.tcp.dialerFor(addr, sintf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info := linkInfoFor("tls", sintf, tcpIDFor(dialer.LocalAddr, addr))
|
|
||||||
if l.links.isConnectedTo(info) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
tlsconfig := l.config.Clone()
|
tlsconfig := l.config.Clone()
|
||||||
tlsconfig.ServerName = sni
|
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
|
||||||
tlsdialer := &tls.Dialer{
|
tlsconfig.ServerName = hostname
|
||||||
NetDialer: dialer,
|
tlsconfig.MinVersion = tls.VersionTLS12
|
||||||
Config: tlsconfig,
|
tlsconfig.MaxVersion = tls.VersionTLS13
|
||||||
}
|
if sni := options.tlsSNI; sni != "" {
|
||||||
conn, err := tlsdialer.DialContext(l.core.ctx, "tcp", addr.String())
|
tlsconfig.ServerName = sni
|
||||||
if err != nil {
|
}
|
||||||
return err
|
addr := &net.TCPAddr{
|
||||||
}
|
IP: ip,
|
||||||
name := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/")
|
Port: port,
|
||||||
dial := &linkDial{
|
}
|
||||||
url: url,
|
dialer, err := l.tcp.dialerFor(addr, info.sintf)
|
||||||
sintf: sintf,
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
return l.handler(dial, name, info, conn, options, false, false)
|
}
|
||||||
|
tlsdialer := &tls.Dialer{
|
||||||
|
NetDialer: dialer,
|
||||||
|
Config: tlsconfig,
|
||||||
|
}
|
||||||
|
return tlsdialer.DialContext(ctx, "tcp", addr.String())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) {
|
func (l *linkTLS) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
|
||||||
ctx, cancel := context.WithCancel(l.core.ctx)
|
|
||||||
hostport := url.Host
|
hostport := url.Host
|
||||||
if sintf != "" {
|
if sintf != "" {
|
||||||
if host, port, err := net.SplitHostPort(hostport); err == nil {
|
if host, port, err := net.SplitHostPort(hostport); err == nil {
|
||||||
|
@ -87,88 +65,8 @@ func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) {
|
||||||
}
|
}
|
||||||
listener, err := l.listener.Listen(ctx, "tcp", hostport)
|
listener, err := l.listener.Listen(ctx, "tcp", hostport)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlslistener := tls.NewListener(listener, l.config)
|
tlslistener := tls.NewListener(listener, l.config)
|
||||||
entry := &Listener{
|
return tlslistener, nil
|
||||||
Listener: tlslistener,
|
|
||||||
closed: make(chan struct{}),
|
|
||||||
}
|
|
||||||
phony.Block(l, func() {
|
|
||||||
l._listeners[entry] = cancel
|
|
||||||
})
|
|
||||||
l.core.log.Printf("TLS listener started on %s", listener.Addr())
|
|
||||||
go func() {
|
|
||||||
defer phony.Block(l, func() {
|
|
||||||
delete(l._listeners, entry)
|
|
||||||
})
|
|
||||||
for {
|
|
||||||
conn, err := tlslistener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
cancel()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
laddr := conn.LocalAddr().(*net.TCPAddr)
|
|
||||||
raddr := conn.RemoteAddr().(*net.TCPAddr)
|
|
||||||
name := fmt.Sprintf("tls://%s", raddr)
|
|
||||||
info := linkInfoFor("tls", sintf, tcpIDFor(laddr, raddr))
|
|
||||||
if err = l.handler(nil, name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil {
|
|
||||||
l.core.log.Errorln("Failed to create inbound link:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ = tlslistener.Close()
|
|
||||||
close(entry.closed)
|
|
||||||
l.core.log.Printf("TLS listener stopped on %s", listener.Addr())
|
|
||||||
}()
|
|
||||||
return entry, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC5280 section 4.1.2.5
|
|
||||||
var notAfterNeverExpires = time.Date(9999, time.December, 31, 23, 59, 59, 0, time.UTC)
|
|
||||||
|
|
||||||
func (l *linkTLS) generateConfig() (*tls.Config, error) {
|
|
||||||
certBuf := &bytes.Buffer{}
|
|
||||||
cert := x509.Certificate{
|
|
||||||
SerialNumber: big.NewInt(1),
|
|
||||||
Subject: pkix.Name{
|
|
||||||
CommonName: hex.EncodeToString(l.links.core.public[:]),
|
|
||||||
},
|
|
||||||
NotBefore: time.Now(),
|
|
||||||
NotAfter: notAfterNeverExpires,
|
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
||||||
BasicConstraintsValid: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
certbytes, err := x509.CreateCertificate(rand.Reader, &cert, &cert, l.links.core.public, l.links.core.secret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pem.Encode(certBuf, &pem.Block{
|
|
||||||
Type: "CERTIFICATE",
|
|
||||||
Bytes: certbytes,
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rootCAs := x509.NewCertPool()
|
|
||||||
rootCAs.AppendCertsFromPEM(certbytes)
|
|
||||||
|
|
||||||
return &tls.Config{
|
|
||||||
RootCAs: rootCAs,
|
|
||||||
Certificates: []tls.Certificate{
|
|
||||||
{
|
|
||||||
Certificate: [][]byte{certbytes},
|
|
||||||
PrivateKey: l.links.core.secret,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
MinVersion: tls.VersionTLS13,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *linkTLS) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error {
|
|
||||||
return l.tcp.handler(dial, name, info, conn, options, incoming, force)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,8 @@ import (
|
||||||
type linkUNIX struct {
|
type linkUNIX struct {
|
||||||
phony.Inbox
|
phony.Inbox
|
||||||
*links
|
*links
|
||||||
dialer *net.Dialer
|
dialer *net.Dialer
|
||||||
listener *net.ListenConfig
|
listener *net.ListenConfig
|
||||||
_listeners map[*Listener]context.CancelFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *links) newLinkUNIX() *linkUNIX {
|
func (l *links) newLinkUNIX() *linkUNIX {
|
||||||
|
@ -27,75 +26,18 @@ func (l *links) newLinkUNIX() *linkUNIX {
|
||||||
listener: &net.ListenConfig{
|
listener: &net.ListenConfig{
|
||||||
KeepAlive: -1,
|
KeepAlive: -1,
|
||||||
},
|
},
|
||||||
_listeners: map[*Listener]context.CancelFunc{},
|
|
||||||
}
|
}
|
||||||
return lt
|
return lt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkUNIX) dial(url *url.URL, options linkOptions, _ string) error {
|
func (l *linkUNIX) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||||
info := linkInfoFor("unix", "", url.Path)
|
|
||||||
if l.links.isConnectedTo(info) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
addr, err := net.ResolveUnixAddr("unix", url.Path)
|
addr, err := net.ResolveUnixAddr("unix", url.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
conn, err := l.dialer.DialContext(l.core.ctx, "unix", addr.String())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dial := &linkDial{
|
|
||||||
url: url,
|
|
||||||
}
|
|
||||||
return l.handler(dial, url.String(), info, conn, options, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *linkUNIX) listen(url *url.URL, _ string) (*Listener, error) {
|
|
||||||
ctx, cancel := context.WithCancel(l.core.ctx)
|
|
||||||
listener, err := l.listener.Listen(ctx, "unix", url.Path)
|
|
||||||
if err != nil {
|
|
||||||
cancel()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
entry := &Listener{
|
return l.dialer.DialContext(ctx, "unix", addr.String())
|
||||||
Listener: listener,
|
|
||||||
closed: make(chan struct{}),
|
|
||||||
}
|
|
||||||
phony.Block(l, func() {
|
|
||||||
l._listeners[entry] = cancel
|
|
||||||
})
|
|
||||||
l.core.log.Printf("UNIX listener started on %s", listener.Addr())
|
|
||||||
go func() {
|
|
||||||
defer phony.Block(l, func() {
|
|
||||||
delete(l._listeners, entry)
|
|
||||||
})
|
|
||||||
for {
|
|
||||||
conn, err := listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
cancel()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
info := linkInfoFor("unix", "", url.String())
|
|
||||||
if err = l.handler(nil, url.String(), info, conn, linkOptionsForListener(url), true); err != nil {
|
|
||||||
l.core.log.Errorln("Failed to create inbound link:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ = listener.Close()
|
|
||||||
close(entry.closed)
|
|
||||||
l.core.log.Printf("UNIX listener stopped on %s", listener.Addr())
|
|
||||||
}()
|
|
||||||
return entry, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkUNIX) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error {
|
func (l *linkUNIX) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
||||||
return l.links.create(
|
return l.listener.Listen(ctx, "unix", url.Path)
|
||||||
conn, // connection
|
|
||||||
dial, // connection URL
|
|
||||||
name, // connection name
|
|
||||||
info, // connection info
|
|
||||||
incoming, // not incoming
|
|
||||||
false, // not forced
|
|
||||||
options, // connection options
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
148
src/core/link_ws.go
Normal file
148
src/core/link_ws.go
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Arceliar/phony"
|
||||||
|
"github.com/coder/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type linkWS struct {
|
||||||
|
phony.Inbox
|
||||||
|
*links
|
||||||
|
listenconfig *net.ListenConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type linkWSConn struct {
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type linkWSListener struct {
|
||||||
|
ch chan *linkWSConn
|
||||||
|
ctx context.Context
|
||||||
|
httpServer *http.Server
|
||||||
|
listener net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
type wsServer struct {
|
||||||
|
ch chan *linkWSConn
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linkWSListener) Accept() (net.Conn, error) {
|
||||||
|
qs := <-l.ch
|
||||||
|
if qs == nil {
|
||||||
|
return nil, context.Canceled
|
||||||
|
}
|
||||||
|
return qs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linkWSListener) Addr() net.Addr {
|
||||||
|
return l.listener.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linkWSListener) Close() error {
|
||||||
|
if err := l.httpServer.Shutdown(l.ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return l.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *wsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/health" || r.URL.Path == "/healthz" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte("OK"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
|
||||||
|
Subprotocols: []string{"ygg-ws"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Subprotocol() != "ygg-ws" {
|
||||||
|
c.Close(websocket.StatusPolicyViolation, "client must speak the ygg-ws subprotocol")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ch <- &linkWSConn{
|
||||||
|
Conn: websocket.NetConn(s.ctx, c, websocket.MessageBinary),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *links) newLinkWS() *linkWS {
|
||||||
|
lt := &linkWS{
|
||||||
|
links: l,
|
||||||
|
listenconfig: &net.ListenConfig{
|
||||||
|
KeepAlive: -1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return lt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linkWS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||||
|
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
|
||||||
|
u := *url
|
||||||
|
u.Host = net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
|
||||||
|
addr := &net.TCPAddr{
|
||||||
|
IP: ip,
|
||||||
|
Port: port,
|
||||||
|
}
|
||||||
|
dialer, err := l.tcp.dialerFor(addr, info.sintf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
wsconn, _, err := websocket.Dial(ctx, u.String(), &websocket.DialOptions{
|
||||||
|
HTTPClient: &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
Dial: dialer.Dial,
|
||||||
|
DialContext: dialer.DialContext,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Subprotocols: []string{"ygg-ws"},
|
||||||
|
Host: hostname,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &linkWSConn{
|
||||||
|
Conn: websocket.NetConn(ctx, wsconn, websocket.MessageBinary),
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linkWS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
||||||
|
nl, err := l.listenconfig.Listen(ctx, "tcp", url.Host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan *linkWSConn)
|
||||||
|
|
||||||
|
httpServer := &http.Server{
|
||||||
|
Handler: &wsServer{
|
||||||
|
ch: ch,
|
||||||
|
ctx: ctx,
|
||||||
|
},
|
||||||
|
BaseContext: func(_ net.Listener) context.Context { return ctx },
|
||||||
|
ReadTimeout: time.Second * 10,
|
||||||
|
WriteTimeout: time.Second * 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
lwl := &linkWSListener{
|
||||||
|
ch: ch,
|
||||||
|
ctx: ctx,
|
||||||
|
httpServer: httpServer,
|
||||||
|
listener: nl,
|
||||||
|
}
|
||||||
|
go lwl.httpServer.Serve(nl) // nolint:errcheck
|
||||||
|
return lwl, nil
|
||||||
|
}
|
72
src/core/link_wss.go
Normal file
72
src/core/link_wss.go
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/Arceliar/phony"
|
||||||
|
"github.com/coder/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type linkWSS struct {
|
||||||
|
phony.Inbox
|
||||||
|
*links
|
||||||
|
tlsconfig *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
type linkWSSConn struct {
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *links) newLinkWSS() *linkWSS {
|
||||||
|
lwss := &linkWSS{
|
||||||
|
links: l,
|
||||||
|
tlsconfig: l.core.config.tls.Clone(),
|
||||||
|
}
|
||||||
|
return lwss
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linkWSS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||||
|
tlsconfig := l.tlsconfig.Clone()
|
||||||
|
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
|
||||||
|
tlsconfig.ServerName = hostname
|
||||||
|
tlsconfig.MinVersion = tls.VersionTLS12
|
||||||
|
tlsconfig.MaxVersion = tls.VersionTLS13
|
||||||
|
u := *url
|
||||||
|
u.Host = net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
|
||||||
|
addr := &net.TCPAddr{
|
||||||
|
IP: ip,
|
||||||
|
Port: port,
|
||||||
|
}
|
||||||
|
dialer, err := l.tcp.dialerFor(addr, info.sintf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
wsconn, _, err := websocket.Dial(ctx, u.String(), &websocket.DialOptions{
|
||||||
|
HTTPClient: &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
Dial: dialer.Dial,
|
||||||
|
DialContext: dialer.DialContext,
|
||||||
|
TLSClientConfig: tlsconfig,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Subprotocols: []string{"ygg-ws"},
|
||||||
|
Host: hostname,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &linkWSSConn{
|
||||||
|
Conn: websocket.NetConn(ctx, wsconn, websocket.MessageBinary),
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *linkWSS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
||||||
|
return nil, fmt.Errorf("WSS listener not supported, use WS listener behind reverse proxy instead")
|
||||||
|
}
|
|
@ -2,14 +2,31 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Core) _applyOption(opt SetupOption) {
|
func (c *Core) _applyOption(opt SetupOption) (err error) {
|
||||||
switch v := opt.(type) {
|
switch v := opt.(type) {
|
||||||
case Peer:
|
case Peer:
|
||||||
c.config._peers[v] = nil
|
u, err := url.Parse(v.URI)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse peering URI: %w", err)
|
||||||
|
}
|
||||||
|
err = c.links.add(u, v.SourceInterface, linkTypePersistent)
|
||||||
|
switch err {
|
||||||
|
case ErrLinkAlreadyConfigured:
|
||||||
|
// Don't return this error, otherwise we'll panic at startup
|
||||||
|
// if there are multiple of the same peer configured
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
case ListenAddress:
|
case ListenAddress:
|
||||||
c.config._listeners[v] = struct{}{}
|
c.config._listeners[v] = struct{}{}
|
||||||
|
case PeerFilter:
|
||||||
|
c.config.peerFilter = v
|
||||||
case NodeInfo:
|
case NodeInfo:
|
||||||
c.config.nodeinfo = v
|
c.config.nodeinfo = v
|
||||||
case NodeInfoPrivacy:
|
case NodeInfoPrivacy:
|
||||||
|
@ -19,6 +36,7 @@ func (c *Core) _applyOption(opt SetupOption) {
|
||||||
copy(pk[:], v)
|
copy(pk[:], v)
|
||||||
c.config._allowedPublicKeys[pk] = struct{}{}
|
c.config._allowedPublicKeys[pk] = struct{}{}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type SetupOption interface {
|
type SetupOption interface {
|
||||||
|
@ -33,9 +51,11 @@ type Peer struct {
|
||||||
type NodeInfo map[string]interface{}
|
type NodeInfo map[string]interface{}
|
||||||
type NodeInfoPrivacy bool
|
type NodeInfoPrivacy bool
|
||||||
type AllowedPublicKey ed25519.PublicKey
|
type AllowedPublicKey ed25519.PublicKey
|
||||||
|
type PeerFilter func(net.IP) bool
|
||||||
|
|
||||||
func (a ListenAddress) isSetupOption() {}
|
func (a ListenAddress) isSetupOption() {}
|
||||||
func (a Peer) isSetupOption() {}
|
func (a Peer) isSetupOption() {}
|
||||||
func (a NodeInfo) isSetupOption() {}
|
func (a NodeInfo) isSetupOption() {}
|
||||||
func (a NodeInfoPrivacy) isSetupOption() {}
|
func (a NodeInfoPrivacy) isSetupOption() {}
|
||||||
func (a AllowedPublicKey) isSetupOption() {}
|
func (a AllowedPublicKey) isSetupOption() {}
|
||||||
|
func (a PeerFilter) isSetupOption() {}
|
||||||
|
|
41
src/core/options_test.go
Normal file
41
src/core/options_test.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests that duplicate peers in the configuration file
|
||||||
|
// won't cause an error when the node starts. Otherwise
|
||||||
|
// we can panic unnecessarily.
|
||||||
|
func TestDuplicatePeerAtStartup(t *testing.T) {
|
||||||
|
cfg := config.GenerateConfig()
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
cfg.Peers = append(cfg.Peers, "tcp://1.2.3.4:4321")
|
||||||
|
}
|
||||||
|
if _, err := New(cfg.Certificate, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that duplicate peers given to us through the
|
||||||
|
// API will still error as expected, even if they didn't
|
||||||
|
// at startup. We expect to notify the user through the
|
||||||
|
// admin socket if they try to add a peer that is already
|
||||||
|
// configured.
|
||||||
|
func TestDuplicatePeerFromAPI(t *testing.T) {
|
||||||
|
cfg := config.GenerateConfig()
|
||||||
|
c, err := New(cfg.Certificate, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
u, _ := url.Parse("tcp://1.2.3.4:4321")
|
||||||
|
if err := c.AddPeer(u, ""); err != nil {
|
||||||
|
t.Fatalf("Adding peer failed on first attempt: %s", err)
|
||||||
|
}
|
||||||
|
if err := c.AddPeer(u, ""); err == nil {
|
||||||
|
t.Fatalf("Adding peer should have failed on second attempt")
|
||||||
|
}
|
||||||
|
}
|
17
src/core/pool.go
Normal file
17
src/core/pool.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
var bytePool = sync.Pool{New: func() interface{} { return []byte(nil) }}
|
||||||
|
|
||||||
|
func allocBytes(size int) []byte {
|
||||||
|
bs := bytePool.Get().([]byte)
|
||||||
|
if cap(bs) < size {
|
||||||
|
bs = make([]byte, size)
|
||||||
|
}
|
||||||
|
return bs[:size]
|
||||||
|
}
|
||||||
|
|
||||||
|
func freeBytes(bs []byte) {
|
||||||
|
bytePool.Put(bs[:0]) //nolint:staticcheck
|
||||||
|
}
|
|
@ -21,8 +21,8 @@ const (
|
||||||
typeDebugGetSelfResponse
|
typeDebugGetSelfResponse
|
||||||
typeDebugGetPeersRequest
|
typeDebugGetPeersRequest
|
||||||
typeDebugGetPeersResponse
|
typeDebugGetPeersResponse
|
||||||
typeDebugGetDHTRequest
|
typeDebugGetTreeRequest
|
||||||
typeDebugGetDHTResponse
|
typeDebugGetTreeResponse
|
||||||
)
|
)
|
||||||
|
|
||||||
type reqInfo struct {
|
type reqInfo struct {
|
||||||
|
@ -40,7 +40,7 @@ type protoHandler struct {
|
||||||
|
|
||||||
selfRequests map[keyArray]*reqInfo
|
selfRequests map[keyArray]*reqInfo
|
||||||
peersRequests map[keyArray]*reqInfo
|
peersRequests map[keyArray]*reqInfo
|
||||||
dhtRequests map[keyArray]*reqInfo
|
treeRequests map[keyArray]*reqInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *protoHandler) init(core *Core) {
|
func (p *protoHandler) init(core *Core) {
|
||||||
|
@ -49,7 +49,7 @@ func (p *protoHandler) init(core *Core) {
|
||||||
|
|
||||||
p.selfRequests = make(map[keyArray]*reqInfo)
|
p.selfRequests = make(map[keyArray]*reqInfo)
|
||||||
p.peersRequests = make(map[keyArray]*reqInfo)
|
p.peersRequests = make(map[keyArray]*reqInfo)
|
||||||
p.dhtRequests = make(map[keyArray]*reqInfo)
|
p.treeRequests = make(map[keyArray]*reqInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common functions
|
// Common functions
|
||||||
|
@ -89,10 +89,10 @@ func (p *protoHandler) _handleDebug(key keyArray, bs []byte) {
|
||||||
p._handleGetPeersRequest(key)
|
p._handleGetPeersRequest(key)
|
||||||
case typeDebugGetPeersResponse:
|
case typeDebugGetPeersResponse:
|
||||||
p._handleGetPeersResponse(key, bs[1:])
|
p._handleGetPeersResponse(key, bs[1:])
|
||||||
case typeDebugGetDHTRequest:
|
case typeDebugGetTreeRequest:
|
||||||
p._handleGetDHTRequest(key)
|
p._handleGetTreeRequest(key)
|
||||||
case typeDebugGetDHTResponse:
|
case typeDebugGetTreeResponse:
|
||||||
p._handleGetDHTResponse(key, bs[1:])
|
p._handleGetTreeResponse(key, bs[1:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,8 +126,8 @@ func (p *protoHandler) sendGetSelfRequest(key keyArray, callback func([]byte)) {
|
||||||
func (p *protoHandler) _handleGetSelfRequest(key keyArray) {
|
func (p *protoHandler) _handleGetSelfRequest(key keyArray) {
|
||||||
self := p.core.GetSelf()
|
self := p.core.GetSelf()
|
||||||
res := map[string]string{
|
res := map[string]string{
|
||||||
"key": hex.EncodeToString(self.Key[:]),
|
"key": hex.EncodeToString(self.Key[:]),
|
||||||
"coords": fmt.Sprintf("%v", self.Coords),
|
"routing_entries": fmt.Sprintf("%v", self.RoutingEntries),
|
||||||
}
|
}
|
||||||
bs, err := json.Marshal(res) // FIXME this puts keys in base64, not hex
|
bs, err := json.Marshal(res) // FIXME this puts keys in base64, not hex
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -188,47 +188,47 @@ func (p *protoHandler) _handleGetPeersResponse(key keyArray, bs []byte) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get DHT
|
// Get Tree
|
||||||
|
|
||||||
func (p *protoHandler) sendGetDHTRequest(key keyArray, callback func([]byte)) {
|
func (p *protoHandler) sendGetTreeRequest(key keyArray, callback func([]byte)) {
|
||||||
p.Act(nil, func() {
|
p.Act(nil, func() {
|
||||||
if info := p.dhtRequests[key]; info != nil {
|
if info := p.treeRequests[key]; info != nil {
|
||||||
info.timer.Stop()
|
info.timer.Stop()
|
||||||
delete(p.dhtRequests, key)
|
delete(p.treeRequests, key)
|
||||||
}
|
}
|
||||||
info := new(reqInfo)
|
info := new(reqInfo)
|
||||||
info.callback = callback
|
info.callback = callback
|
||||||
info.timer = time.AfterFunc(time.Minute, func() {
|
info.timer = time.AfterFunc(time.Minute, func() {
|
||||||
p.Act(nil, func() {
|
p.Act(nil, func() {
|
||||||
if p.dhtRequests[key] == info {
|
if p.treeRequests[key] == info {
|
||||||
delete(p.dhtRequests, key)
|
delete(p.treeRequests, key)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
p.dhtRequests[key] = info
|
p.treeRequests[key] = info
|
||||||
p._sendDebug(key, typeDebugGetDHTRequest, nil)
|
p._sendDebug(key, typeDebugGetTreeRequest, nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *protoHandler) _handleGetDHTRequest(key keyArray) {
|
func (p *protoHandler) _handleGetTreeRequest(key keyArray) {
|
||||||
dinfos := p.core.GetDHT()
|
dinfos := p.core.GetTree()
|
||||||
var bs []byte
|
var bs []byte
|
||||||
for _, dinfo := range dinfos {
|
for _, dinfo := range dinfos {
|
||||||
tmp := append(bs, dinfo.Key[:]...)
|
tmp := append(bs, dinfo.Key[:]...)
|
||||||
const responseOverhead = 2 // 1 debug type, 1 getdht type
|
const responseOverhead = 2 // 1 debug type, 1 gettree type
|
||||||
if uint64(len(tmp))+responseOverhead > p.core.MTU() {
|
if uint64(len(tmp))+responseOverhead > p.core.MTU() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
bs = tmp
|
bs = tmp
|
||||||
}
|
}
|
||||||
p._sendDebug(key, typeDebugGetDHTResponse, bs)
|
p._sendDebug(key, typeDebugGetTreeResponse, bs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *protoHandler) _handleGetDHTResponse(key keyArray, bs []byte) {
|
func (p *protoHandler) _handleGetTreeResponse(key keyArray, bs []byte) {
|
||||||
if info := p.dhtRequests[key]; info != nil {
|
if info := p.treeRequests[key]; info != nil {
|
||||||
info.timer.Stop()
|
info.timer.Stop()
|
||||||
info.callback(bs)
|
info.callback(bs)
|
||||||
delete(p.dhtRequests, key)
|
delete(p.treeRequests, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,15 +251,16 @@ func (p *protoHandler) getSelfHandler(in json.RawMessage) (interface{}, error) {
|
||||||
if kbs, err = hex.DecodeString(req.Key); err != nil {
|
if kbs, err = hex.DecodeString(req.Key); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(kbs) != ed25519.PublicKeySize {
|
||||||
|
return nil, fmt.Errorf("invalid public key length")
|
||||||
|
}
|
||||||
copy(key[:], kbs)
|
copy(key[:], kbs)
|
||||||
ch := make(chan []byte, 1)
|
ch := make(chan []byte, 1)
|
||||||
p.sendGetSelfRequest(key, func(info []byte) {
|
p.sendGetSelfRequest(key, func(info []byte) {
|
||||||
ch <- info
|
ch <- info
|
||||||
})
|
})
|
||||||
timer := time.NewTimer(6 * time.Second)
|
|
||||||
defer timer.Stop()
|
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-time.After(6 * time.Second):
|
||||||
return nil, errors.New("timeout")
|
return nil, errors.New("timeout")
|
||||||
case info := <-ch:
|
case info := <-ch:
|
||||||
var msg json.RawMessage
|
var msg json.RawMessage
|
||||||
|
@ -291,15 +292,16 @@ func (p *protoHandler) getPeersHandler(in json.RawMessage) (interface{}, error)
|
||||||
if kbs, err = hex.DecodeString(req.Key); err != nil {
|
if kbs, err = hex.DecodeString(req.Key); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(kbs) != ed25519.PublicKeySize {
|
||||||
|
return nil, fmt.Errorf("invalid public key length")
|
||||||
|
}
|
||||||
copy(key[:], kbs)
|
copy(key[:], kbs)
|
||||||
ch := make(chan []byte, 1)
|
ch := make(chan []byte, 1)
|
||||||
p.sendGetPeersRequest(key, func(info []byte) {
|
p.sendGetPeersRequest(key, func(info []byte) {
|
||||||
ch <- info
|
ch <- info
|
||||||
})
|
})
|
||||||
timer := time.NewTimer(6 * time.Second)
|
|
||||||
defer timer.Stop()
|
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-time.After(6 * time.Second):
|
||||||
return nil, errors.New("timeout")
|
return nil, errors.New("timeout")
|
||||||
case info := <-ch:
|
case info := <-ch:
|
||||||
ks := make(map[string][]string)
|
ks := make(map[string][]string)
|
||||||
|
@ -322,16 +324,16 @@ func (p *protoHandler) getPeersHandler(in json.RawMessage) (interface{}, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Admin socket stuff for "Get DHT"
|
// Admin socket stuff for "Get Tree"
|
||||||
|
|
||||||
type DebugGetDHTRequest struct {
|
type DebugGetTreeRequest struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DebugGetDHTResponse map[string]interface{}
|
type DebugGetTreeResponse map[string]interface{}
|
||||||
|
|
||||||
func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) {
|
func (p *protoHandler) getTreeHandler(in json.RawMessage) (interface{}, error) {
|
||||||
var req DebugGetDHTRequest
|
var req DebugGetTreeRequest
|
||||||
if err := json.Unmarshal(in, &req); err != nil {
|
if err := json.Unmarshal(in, &req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -341,15 +343,16 @@ func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) {
|
||||||
if kbs, err = hex.DecodeString(req.Key); err != nil {
|
if kbs, err = hex.DecodeString(req.Key); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(kbs) != ed25519.PublicKeySize {
|
||||||
|
return nil, fmt.Errorf("invalid public key length")
|
||||||
|
}
|
||||||
copy(key[:], kbs)
|
copy(key[:], kbs)
|
||||||
ch := make(chan []byte, 1)
|
ch := make(chan []byte, 1)
|
||||||
p.sendGetDHTRequest(key, func(info []byte) {
|
p.sendGetTreeRequest(key, func(info []byte) {
|
||||||
ch <- info
|
ch <- info
|
||||||
})
|
})
|
||||||
timer := time.NewTimer(6 * time.Second)
|
|
||||||
defer timer.Stop()
|
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-time.After(6 * time.Second):
|
||||||
return nil, errors.New("timeout")
|
return nil, errors.New("timeout")
|
||||||
case info := <-ch:
|
case info := <-ch:
|
||||||
ks := make(map[string][]string)
|
ks := make(map[string][]string)
|
||||||
|
@ -367,7 +370,7 @@ func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ip := net.IP(address.AddrForKey(kbs)[:])
|
ip := net.IP(address.AddrForKey(kbs)[:])
|
||||||
res := DebugGetDHTResponse{ip.String(): msg}
|
res := DebugGetTreeResponse{ip.String(): msg}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
29
src/core/tls.go
Normal file
29
src/core/tls.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Core) generateTLSConfig(cert *tls.Certificate) (*tls.Config, error) {
|
||||||
|
config := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{*cert},
|
||||||
|
ClientAuth: tls.NoClientCert,
|
||||||
|
GetClientCertificate: func(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
||||||
|
return cert, nil
|
||||||
|
},
|
||||||
|
VerifyPeerCertificate: c.verifyTLSCertificate,
|
||||||
|
VerifyConnection: c.verifyTLSConnection,
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
MinVersion: tls.VersionTLS13,
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) verifyTLSCertificate(_ [][]byte, _ [][]*x509.Certificate) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) verifyTLSConnection(_ tls.ConnectionState) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -4,65 +4,166 @@ package core
|
||||||
// Used in the initial connection setup and key exchange
|
// Used in the initial connection setup and key exchange
|
||||||
// Some of this could arguably go in wire.go instead
|
// Some of this could arguably go in wire.go instead
|
||||||
|
|
||||||
import "crypto/ed25519"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/blake2b"
|
||||||
|
)
|
||||||
|
|
||||||
// This is the version-specific metadata exchanged at the start of a connection.
|
// This is the version-specific metadata exchanged at the start of a connection.
|
||||||
// It must always begin with the 4 bytes "meta" and a wire formatted uint64 major version number.
|
// It must always begin with the 4 bytes "meta" and a wire formatted uint64 major version number.
|
||||||
// The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open a connection.
|
// The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open a connection.
|
||||||
type version_metadata struct {
|
type version_metadata struct {
|
||||||
meta [4]byte
|
majorVer uint16
|
||||||
ver uint8 // 1 byte in this version
|
minorVer uint16
|
||||||
// Everything after this point potentially depends on the version number, and is subject to change in future versions
|
publicKey ed25519.PublicKey
|
||||||
minorVer uint8 // 1 byte in this version
|
priority uint8
|
||||||
key ed25519.PublicKey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProtocolVersionMajor uint16 = 0
|
||||||
|
ProtocolVersionMinor uint16 = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// Once a major/minor version is released, it is not safe to change any of these
|
||||||
|
// (including their ordering), it is only safe to add new ones.
|
||||||
|
const (
|
||||||
|
metaVersionMajor uint16 = iota // uint16
|
||||||
|
metaVersionMinor // uint16
|
||||||
|
metaPublicKey // [32]byte
|
||||||
|
metaPriority // uint8
|
||||||
|
)
|
||||||
|
|
||||||
|
type handshakeError string
|
||||||
|
|
||||||
|
func (e handshakeError) Error() string { return string(e) }
|
||||||
|
|
||||||
|
const ErrHandshakeInvalidPreamble = handshakeError("invalid handshake, remote side is not Yggdrasil")
|
||||||
|
const ErrHandshakeInvalidLength = handshakeError("invalid handshake length, possible version mismatch")
|
||||||
|
const ErrHandshakeInvalidPassword = handshakeError("invalid password supplied, check your config")
|
||||||
|
const ErrHandshakeHashFailure = handshakeError("invalid hash length")
|
||||||
|
const ErrHandshakeIncorrectPassword = handshakeError("password does not match remote side")
|
||||||
|
|
||||||
// Gets a base metadata with no keys set, but with the correct version numbers.
|
// Gets a base metadata with no keys set, but with the correct version numbers.
|
||||||
func version_getBaseMetadata() version_metadata {
|
func version_getBaseMetadata() version_metadata {
|
||||||
return version_metadata{
|
return version_metadata{
|
||||||
meta: [4]byte{'m', 'e', 't', 'a'},
|
majorVer: ProtocolVersionMajor,
|
||||||
ver: 0,
|
minorVer: ProtocolVersionMinor,
|
||||||
minorVer: 4,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the length of the metadata for this version, used to know how many bytes to read from the start of a connection.
|
|
||||||
func version_getMetaLength() (mlen int) {
|
|
||||||
mlen += 4 // meta
|
|
||||||
mlen++ // ver, as long as it's < 127, which it is in this version
|
|
||||||
mlen++ // minorVer, as long as it's < 127, which it is in this version
|
|
||||||
mlen += ed25519.PublicKeySize // key
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encodes version metadata into its wire format.
|
// Encodes version metadata into its wire format.
|
||||||
func (m *version_metadata) encode() []byte {
|
func (m *version_metadata) encode(privateKey ed25519.PrivateKey, password []byte) ([]byte, error) {
|
||||||
bs := make([]byte, 0, version_getMetaLength())
|
bs := make([]byte, 0, 64)
|
||||||
bs = append(bs, m.meta[:]...)
|
bs = append(bs, 'm', 'e', 't', 'a')
|
||||||
bs = append(bs, m.ver)
|
bs = append(bs, 0, 0) // Remaining message length
|
||||||
bs = append(bs, m.minorVer)
|
|
||||||
bs = append(bs, m.key[:]...)
|
bs = binary.BigEndian.AppendUint16(bs, metaVersionMajor)
|
||||||
if len(bs) != version_getMetaLength() {
|
bs = binary.BigEndian.AppendUint16(bs, 2)
|
||||||
panic("Inconsistent metadata length")
|
bs = binary.BigEndian.AppendUint16(bs, m.majorVer)
|
||||||
|
|
||||||
|
bs = binary.BigEndian.AppendUint16(bs, metaVersionMinor)
|
||||||
|
bs = binary.BigEndian.AppendUint16(bs, 2)
|
||||||
|
bs = binary.BigEndian.AppendUint16(bs, m.minorVer)
|
||||||
|
|
||||||
|
bs = binary.BigEndian.AppendUint16(bs, metaPublicKey)
|
||||||
|
bs = binary.BigEndian.AppendUint16(bs, ed25519.PublicKeySize)
|
||||||
|
bs = append(bs, m.publicKey[:]...)
|
||||||
|
|
||||||
|
bs = binary.BigEndian.AppendUint16(bs, metaPriority)
|
||||||
|
bs = binary.BigEndian.AppendUint16(bs, 1)
|
||||||
|
bs = append(bs, m.priority)
|
||||||
|
|
||||||
|
hasher, err := blake2b.New512(password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return bs
|
n, err := hasher.Write(m.publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n != ed25519.PublicKeySize {
|
||||||
|
return nil, ErrHandshakeHashFailure
|
||||||
|
}
|
||||||
|
hash := hasher.Sum(nil)
|
||||||
|
bs = append(bs, ed25519.Sign(privateKey, hash)...)
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint16(bs[4:6], uint16(len(bs)-6))
|
||||||
|
return bs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decodes version metadata from its wire format into the struct.
|
// Decodes version metadata from its wire format into the struct.
|
||||||
func (m *version_metadata) decode(bs []byte) bool {
|
func (m *version_metadata) decode(r io.Reader, password []byte) error {
|
||||||
if len(bs) != version_getMetaLength() {
|
bh := [6]byte{}
|
||||||
return false
|
if _, err := io.ReadFull(r, bh[:]); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
offset := 0
|
meta := [4]byte{'m', 'e', 't', 'a'}
|
||||||
offset += copy(m.meta[:], bs[offset:])
|
if !bytes.Equal(bh[:4], meta[:]) {
|
||||||
m.ver, offset = bs[offset], offset+1
|
return ErrHandshakeInvalidPreamble
|
||||||
m.minorVer, offset = bs[offset], offset+1
|
}
|
||||||
m.key = append([]byte(nil), bs[offset:]...)
|
hl := binary.BigEndian.Uint16(bh[4:6])
|
||||||
return true
|
if hl < ed25519.SignatureSize {
|
||||||
|
return ErrHandshakeInvalidLength
|
||||||
|
}
|
||||||
|
bs := make([]byte, hl)
|
||||||
|
if _, err := io.ReadFull(r, bs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sig := bs[len(bs)-ed25519.SignatureSize:]
|
||||||
|
bs = bs[:len(bs)-ed25519.SignatureSize]
|
||||||
|
|
||||||
|
for len(bs) >= 4 {
|
||||||
|
op := binary.BigEndian.Uint16(bs[:2])
|
||||||
|
oplen := binary.BigEndian.Uint16(bs[2:4])
|
||||||
|
if bs = bs[4:]; len(bs) < int(oplen) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch op {
|
||||||
|
case metaVersionMajor:
|
||||||
|
m.majorVer = binary.BigEndian.Uint16(bs[:2])
|
||||||
|
|
||||||
|
case metaVersionMinor:
|
||||||
|
m.minorVer = binary.BigEndian.Uint16(bs[:2])
|
||||||
|
|
||||||
|
case metaPublicKey:
|
||||||
|
m.publicKey = make(ed25519.PublicKey, ed25519.PublicKeySize)
|
||||||
|
copy(m.publicKey, bs[:ed25519.PublicKeySize])
|
||||||
|
|
||||||
|
case metaPriority:
|
||||||
|
m.priority = bs[0]
|
||||||
|
}
|
||||||
|
bs = bs[oplen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher, err := blake2b.New512(password)
|
||||||
|
if err != nil {
|
||||||
|
return ErrHandshakeInvalidPassword
|
||||||
|
}
|
||||||
|
n, err := hasher.Write(m.publicKey)
|
||||||
|
if err != nil || n != ed25519.PublicKeySize {
|
||||||
|
return ErrHandshakeHashFailure
|
||||||
|
}
|
||||||
|
hash := hasher.Sum(nil)
|
||||||
|
if !ed25519.Verify(m.publicKey, hash, sig) {
|
||||||
|
return ErrHandshakeIncorrectPassword
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks that the "meta" bytes and the version numbers are the expected values.
|
// Checks that the "meta" bytes and the version numbers are the expected values.
|
||||||
func (m *version_metadata) check() bool {
|
func (m *version_metadata) check() bool {
|
||||||
base := version_getBaseMetadata()
|
switch {
|
||||||
return base.meta == m.meta && base.ver == m.ver && base.minorVer == m.minorVer
|
case m.majorVer != ProtocolVersionMajor:
|
||||||
|
return false
|
||||||
|
case m.minorVer != ProtocolVersionMinor:
|
||||||
|
return false
|
||||||
|
case len(m.publicKey) != ed25519.PublicKeySize:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
78
src/core/version_test.go
Normal file
78
src/core/version_test.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVersionPasswordAuth(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
password1 []byte // The password on node 1
|
||||||
|
password2 []byte // The password on node 2
|
||||||
|
allowed bool // Should the connection have been allowed?
|
||||||
|
}{
|
||||||
|
{nil, nil, true}, // Allow: No passwords (both nil)
|
||||||
|
{nil, []byte(""), true}, // Allow: No passwords (mixed nil and empty string)
|
||||||
|
{nil, []byte("foo"), false}, // Reject: One node has a password, the other doesn't
|
||||||
|
{[]byte("foo"), []byte(""), false}, // Reject: One node has a password, the other doesn't
|
||||||
|
{[]byte("foo"), []byte("foo"), true}, // Allow: Same password
|
||||||
|
{[]byte("foo"), []byte("bar"), false}, // Reject: Different passwords
|
||||||
|
} {
|
||||||
|
pk1, sk1, err := ed25519.GenerateKey(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Node 1 failed to generate key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata1 := &version_metadata{
|
||||||
|
publicKey: pk1,
|
||||||
|
}
|
||||||
|
encoded, err := metadata1.encode(sk1, tt.password1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Node 1 failed to encode metadata: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var decoded version_metadata
|
||||||
|
if allowed := decoded.decode(bytes.NewBuffer(encoded), tt.password2) == nil; allowed != tt.allowed {
|
||||||
|
t.Fatalf("Permutation %q -> %q should have been %v but was %v", tt.password1, tt.password2, tt.allowed, allowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersionRoundtrip(t *testing.T) {
|
||||||
|
for _, password := range [][]byte{
|
||||||
|
nil, []byte(""), []byte("foo"),
|
||||||
|
} {
|
||||||
|
for _, test := range []*version_metadata{
|
||||||
|
{majorVer: 1},
|
||||||
|
{majorVer: 256},
|
||||||
|
{majorVer: 2, minorVer: 4},
|
||||||
|
{majorVer: 2, minorVer: 257},
|
||||||
|
{majorVer: 258, minorVer: 259},
|
||||||
|
{majorVer: 3, minorVer: 5, priority: 6},
|
||||||
|
{majorVer: 260, minorVer: 261, priority: 7},
|
||||||
|
} {
|
||||||
|
// Generate a random public key for each time, since it is
|
||||||
|
// a required field.
|
||||||
|
pk, sk, err := ed25519.GenerateKey(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
test.publicKey = pk
|
||||||
|
meta, err := test.encode(sk, password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
encoded := bytes.NewBuffer(meta)
|
||||||
|
decoded := &version_metadata{}
|
||||||
|
if err := decoded.decode(encoded, password); err != nil {
|
||||||
|
t.Fatalf("failed to decode: %s", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(test, decoded) {
|
||||||
|
t.Fatalf("round-trip failed\nwant: %+v\n got: %+v", test, decoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,60 +0,0 @@
|
||||||
package defaults
|
|
||||||
|
|
||||||
import "github.com/yggdrasil-network/yggdrasil-go/src/config"
|
|
||||||
|
|
||||||
type MulticastInterfaceConfig = config.MulticastInterfaceConfig
|
|
||||||
|
|
||||||
var defaultConfig = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/defaults.defaultConfig=/path/to/config
|
|
||||||
var defaultAdminListen = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/defaults.defaultAdminListen=unix://path/to/sock'
|
|
||||||
|
|
||||||
// Defines which parameters are expected by default for configuration on a
|
|
||||||
// specific platform. These values are populated in the relevant defaults_*.go
|
|
||||||
// for the platform being targeted. They must be set.
|
|
||||||
type platformDefaultParameters struct {
|
|
||||||
// Admin socket
|
|
||||||
DefaultAdminListen string
|
|
||||||
|
|
||||||
// Configuration (used for yggdrasilctl)
|
|
||||||
DefaultConfigFile string
|
|
||||||
|
|
||||||
// Multicast interfaces
|
|
||||||
DefaultMulticastInterfaces []MulticastInterfaceConfig
|
|
||||||
|
|
||||||
// TUN
|
|
||||||
MaximumIfMTU uint64
|
|
||||||
DefaultIfMTU uint64
|
|
||||||
DefaultIfName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDefaults() platformDefaultParameters {
|
|
||||||
defaults := getDefaults()
|
|
||||||
if defaultConfig != "" {
|
|
||||||
defaults.DefaultConfigFile = defaultConfig
|
|
||||||
}
|
|
||||||
if defaultAdminListen != "" {
|
|
||||||
defaults.DefaultAdminListen = defaultAdminListen
|
|
||||||
}
|
|
||||||
return defaults
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generates default configuration and returns a pointer to the resulting
|
|
||||||
// NodeConfig. This is used when outputting the -genconf parameter and also when
|
|
||||||
// using -autoconf.
|
|
||||||
func GenerateConfig() *config.NodeConfig {
|
|
||||||
// Get the defaults for the platform.
|
|
||||||
defaults := GetDefaults()
|
|
||||||
// Create a node configuration and populate it.
|
|
||||||
cfg := new(config.NodeConfig)
|
|
||||||
cfg.NewKeys()
|
|
||||||
cfg.Listen = []string{}
|
|
||||||
cfg.AdminListen = defaults.DefaultAdminListen
|
|
||||||
cfg.Peers = []string{}
|
|
||||||
cfg.InterfacePeers = map[string][]string{}
|
|
||||||
cfg.AllowedPublicKeys = []string{}
|
|
||||||
cfg.MulticastInterfaces = defaults.DefaultMulticastInterfaces
|
|
||||||
cfg.IfName = defaults.DefaultIfName
|
|
||||||
cfg.IfMTU = defaults.DefaultIfMTU
|
|
||||||
cfg.NodeInfoPrivacy = false
|
|
||||||
|
|
||||||
return cfg
|
|
||||||
}
|
|
|
@ -19,12 +19,14 @@ import (
|
||||||
|
|
||||||
const keyStoreTimeout = 2 * time.Minute
|
const keyStoreTimeout = 2 * time.Minute
|
||||||
|
|
||||||
|
/*
|
||||||
// Out-of-band packet types
|
// Out-of-band packet types
|
||||||
const (
|
const (
|
||||||
typeKeyDummy = iota // nolint:deadcode,varcheck
|
typeKeyDummy = iota // nolint:deadcode,varcheck
|
||||||
typeKeyLookup
|
typeKeyLookup
|
||||||
typeKeyResponse
|
typeKeyResponse
|
||||||
)
|
)
|
||||||
|
*/
|
||||||
|
|
||||||
type keyArray [ed25519.PublicKeySize]byte
|
type keyArray [ed25519.PublicKeySize]byte
|
||||||
|
|
||||||
|
@ -57,10 +59,13 @@ func (k *keyStore) init(c *core.Core) {
|
||||||
k.core = c
|
k.core = c
|
||||||
k.address = *address.AddrForKey(k.core.PublicKey())
|
k.address = *address.AddrForKey(k.core.PublicKey())
|
||||||
k.subnet = *address.SubnetForKey(k.core.PublicKey())
|
k.subnet = *address.SubnetForKey(k.core.PublicKey())
|
||||||
if err := k.core.SetOutOfBandHandler(k.oobHandler); err != nil {
|
/*if err := k.core.SetOutOfBandHandler(k.oobHandler); err != nil {
|
||||||
err = fmt.Errorf("tun.core.SetOutOfBandHander: %w", err)
|
err = fmt.Errorf("tun.core.SetOutOfBandHander: %w", err)
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}*/
|
||||||
|
k.core.SetPathNotify(func(key ed25519.PublicKey) {
|
||||||
|
k.update(key)
|
||||||
|
})
|
||||||
k.keyToInfo = make(map[keyArray]*keyInfo)
|
k.keyToInfo = make(map[keyArray]*keyInfo)
|
||||||
k.addrToInfo = make(map[address.Address]*keyInfo)
|
k.addrToInfo = make(map[address.Address]*keyInfo)
|
||||||
k.addrBuffer = make(map[address.Address]*buffer)
|
k.addrBuffer = make(map[address.Address]*buffer)
|
||||||
|
@ -177,7 +182,8 @@ func (k *keyStore) resetTimeout(info *keyInfo) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) {
|
/*
|
||||||
|
func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) { // nolint:unused
|
||||||
if len(data) != 1+ed25519.SignatureSize {
|
if len(data) != 1+ed25519.SignatureSize {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -198,18 +204,26 @@ func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func (k *keyStore) sendKeyLookup(partial ed25519.PublicKey) {
|
func (k *keyStore) sendKeyLookup(partial ed25519.PublicKey) {
|
||||||
sig := ed25519.Sign(k.core.PrivateKey(), partial[:])
|
/*
|
||||||
bs := append([]byte{typeKeyLookup}, sig...)
|
sig := ed25519.Sign(k.core.PrivateKey(), partial[:])
|
||||||
_ = k.core.SendOutOfBand(partial, bs)
|
bs := append([]byte{typeKeyLookup}, sig...)
|
||||||
|
//_ = k.core.SendOutOfBand(partial, bs)
|
||||||
|
_ = bs
|
||||||
|
*/
|
||||||
|
k.core.SendLookup(partial)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *keyStore) sendKeyResponse(dest ed25519.PublicKey) {
|
/*
|
||||||
|
func (k *keyStore) sendKeyResponse(dest ed25519.PublicKey) { // nolint:unused
|
||||||
sig := ed25519.Sign(k.core.PrivateKey(), dest[:])
|
sig := ed25519.Sign(k.core.PrivateKey(), dest[:])
|
||||||
bs := append([]byte{typeKeyResponse}, sig...)
|
bs := append([]byte{typeKeyResponse}, sig...)
|
||||||
_ = k.core.SendOutOfBand(dest, bs)
|
//_ = k.core.SendOutOfBand(dest, bs)
|
||||||
|
_ = bs
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func (k *keyStore) readPC(p []byte) (int, error) {
|
func (k *keyStore) readPC(p []byte) (int, error) {
|
||||||
buf := make([]byte, k.core.MTU(), 65535)
|
buf := make([]byte, k.core.MTU(), 65535)
|
||||||
|
|
|
@ -2,20 +2,47 @@ package multicast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Arceliar/phony"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GetMulticastInterfacesRequest struct{}
|
type GetMulticastInterfacesRequest struct{}
|
||||||
type GetMulticastInterfacesResponse struct {
|
type GetMulticastInterfacesResponse struct {
|
||||||
Interfaces []string `json:"multicast_interfaces"`
|
Interfaces []MulticastInterfaceState `json:"multicast_interfaces"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Multicast) getMulticastInterfacesHandler(req *GetMulticastInterfacesRequest, res *GetMulticastInterfacesResponse) error {
|
type MulticastInterfaceState struct {
|
||||||
res.Interfaces = []string{}
|
Name string `json:"name"`
|
||||||
for _, v := range m.Interfaces() {
|
Address string `json:"address"`
|
||||||
res.Interfaces = append(res.Interfaces, v.Name)
|
Beacon bool `json:"beacon"`
|
||||||
}
|
Listen bool `json:"listen"`
|
||||||
|
Password bool `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Multicast) getMulticastInterfacesHandler(_ *GetMulticastInterfacesRequest, res *GetMulticastInterfacesResponse) error {
|
||||||
|
res.Interfaces = []MulticastInterfaceState{}
|
||||||
|
phony.Block(m, func() {
|
||||||
|
for name, intf := range m._interfaces {
|
||||||
|
is := MulticastInterfaceState{
|
||||||
|
Name: intf.iface.Name,
|
||||||
|
Beacon: intf.beacon,
|
||||||
|
Listen: intf.listen,
|
||||||
|
Password: len(intf.password) > 0,
|
||||||
|
}
|
||||||
|
if li := m._listeners[name]; li != nil && li.listener != nil {
|
||||||
|
is.Address = li.listener.Addr().String()
|
||||||
|
} else {
|
||||||
|
is.Address = "-"
|
||||||
|
}
|
||||||
|
res.Interfaces = append(res.Interfaces, is)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
slices.SortStableFunc(res.Interfaces, func(a, b MulticastInterfaceState) int {
|
||||||
|
return strings.Compare(a.Name, b.Name)
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
39
src/multicast/advertisement.go
Normal file
39
src/multicast/advertisement.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package multicast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type multicastAdvertisement struct {
|
||||||
|
MajorVersion uint16
|
||||||
|
MinorVersion uint16
|
||||||
|
PublicKey ed25519.PublicKey
|
||||||
|
Port uint16
|
||||||
|
Hash []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *multicastAdvertisement) MarshalBinary() ([]byte, error) {
|
||||||
|
b := make([]byte, 0, ed25519.PublicKeySize+8+len(m.Hash))
|
||||||
|
b = binary.BigEndian.AppendUint16(b, m.MajorVersion)
|
||||||
|
b = binary.BigEndian.AppendUint16(b, m.MinorVersion)
|
||||||
|
b = append(b, m.PublicKey...)
|
||||||
|
b = binary.BigEndian.AppendUint16(b, m.Port)
|
||||||
|
b = binary.BigEndian.AppendUint16(b, uint16(len(m.Hash)))
|
||||||
|
b = append(b, m.Hash...)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *multicastAdvertisement) UnmarshalBinary(b []byte) error {
|
||||||
|
if len(b) < ed25519.PublicKeySize+8 {
|
||||||
|
return fmt.Errorf("invalid multicast beacon")
|
||||||
|
}
|
||||||
|
m.MajorVersion = binary.BigEndian.Uint16(b[0:2])
|
||||||
|
m.MinorVersion = binary.BigEndian.Uint16(b[2:4])
|
||||||
|
m.PublicKey = append(m.PublicKey[:0], b[4:4+ed25519.PublicKeySize]...)
|
||||||
|
m.Port = binary.BigEndian.Uint16(b[4+ed25519.PublicKeySize : 6+ed25519.PublicKeySize])
|
||||||
|
dl := binary.BigEndian.Uint16(b[6+ed25519.PublicKeySize : 8+ed25519.PublicKeySize])
|
||||||
|
m.Hash = append(m.Hash[:0], b[8+ed25519.PublicKeySize:8+ed25519.PublicKeySize+dl]...)
|
||||||
|
return nil
|
||||||
|
}
|
38
src/multicast/advertisement_test.go
Normal file
38
src/multicast/advertisement_test.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package multicast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMulticastAdvertisementRoundTrip(t *testing.T) {
|
||||||
|
pk, sk, err := ed25519.GenerateKey(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
orig := multicastAdvertisement{
|
||||||
|
MajorVersion: 1,
|
||||||
|
MinorVersion: 2,
|
||||||
|
PublicKey: pk,
|
||||||
|
Port: 3,
|
||||||
|
Hash: sk, // any bytes will do
|
||||||
|
}
|
||||||
|
|
||||||
|
ob, err := orig.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var new multicastAdvertisement
|
||||||
|
if err := new.UnmarshalBinary(ob); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(orig, new) {
|
||||||
|
t.Logf("original: %+v", orig)
|
||||||
|
t.Logf("new: %+v", new)
|
||||||
|
t.Fatalf("differences found after round-trip")
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,17 +4,20 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Arceliar/phony"
|
"github.com/Arceliar/phony"
|
||||||
"github.com/gologme/log"
|
"github.com/gologme/log"
|
||||||
|
"github.com/wlynxg/anet"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||||
|
"golang.org/x/crypto/blake2b"
|
||||||
"golang.org/x/net/ipv6"
|
"golang.org/x/net/ipv6"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,7 +30,7 @@ type Multicast struct {
|
||||||
core *core.Core
|
core *core.Core
|
||||||
log *log.Logger
|
log *log.Logger
|
||||||
sock *ipv6.PacketConn
|
sock *ipv6.PacketConn
|
||||||
_isOpen bool
|
running atomic.Bool
|
||||||
_listeners map[string]*listenerInfo
|
_listeners map[string]*listenerInfo
|
||||||
_interfaces map[string]*interfaceInfo
|
_interfaces map[string]*interfaceInfo
|
||||||
_timer *time.Timer
|
_timer *time.Timer
|
||||||
|
@ -44,6 +47,8 @@ type interfaceInfo struct {
|
||||||
listen bool
|
listen bool
|
||||||
port uint16
|
port uint16
|
||||||
priority uint8
|
priority uint8
|
||||||
|
password []byte
|
||||||
|
hash []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type listenerInfo struct {
|
type listenerInfo struct {
|
||||||
|
@ -76,7 +81,7 @@ func New(core *core.Core, log *log.Logger, opts ...SetupOption) (*Multicast, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Multicast) _start() error {
|
func (m *Multicast) _start() error {
|
||||||
if m._isOpen {
|
if !m.running.CompareAndSwap(false, true) {
|
||||||
return fmt.Errorf("multicast module is already started")
|
return fmt.Errorf("multicast module is already started")
|
||||||
}
|
}
|
||||||
var anyEnabled bool
|
var anyEnabled bool
|
||||||
|
@ -84,12 +89,14 @@ func (m *Multicast) _start() error {
|
||||||
anyEnabled = anyEnabled || intf.Beacon || intf.Listen
|
anyEnabled = anyEnabled || intf.Beacon || intf.Listen
|
||||||
}
|
}
|
||||||
if !anyEnabled {
|
if !anyEnabled {
|
||||||
|
m.running.Store(false)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
m.log.Debugln("Starting multicast module")
|
m.log.Debugln("Starting multicast module")
|
||||||
defer m.log.Debugln("Started multicast module")
|
defer m.log.Debugln("Started multicast module")
|
||||||
addr, err := net.ResolveUDPAddr("udp", string(m.config._groupAddr))
|
addr, err := net.ResolveUDPAddr("udp", string(m.config._groupAddr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
m.running.Store(false)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
listenString := fmt.Sprintf("[::]:%v", addr.Port)
|
listenString := fmt.Sprintf("[::]:%v", addr.Port)
|
||||||
|
@ -98,6 +105,7 @@ func (m *Multicast) _start() error {
|
||||||
}
|
}
|
||||||
conn, err := lc.ListenPacket(context.Background(), "udp6", listenString)
|
conn, err := lc.ListenPacket(context.Background(), "udp6", listenString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
m.running.Store(false)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.sock = ipv6.NewPacketConn(conn)
|
m.sock = ipv6.NewPacketConn(conn)
|
||||||
|
@ -105,7 +113,6 @@ func (m *Multicast) _start() error {
|
||||||
// Windows can't set this flag, so we need to handle it in other ways
|
// Windows can't set this flag, so we need to handle it in other ways
|
||||||
}
|
}
|
||||||
|
|
||||||
m._isOpen = true
|
|
||||||
go m.listen()
|
go m.listen()
|
||||||
m.Act(nil, m._multicastStarted)
|
m.Act(nil, m._multicastStarted)
|
||||||
m.Act(nil, m._announce)
|
m.Act(nil, m._announce)
|
||||||
|
@ -115,11 +122,7 @@ func (m *Multicast) _start() error {
|
||||||
|
|
||||||
// IsStarted returns true if the module has been started.
|
// IsStarted returns true if the module has been started.
|
||||||
func (m *Multicast) IsStarted() bool {
|
func (m *Multicast) IsStarted() bool {
|
||||||
var isOpen bool
|
return m.running.Load()
|
||||||
phony.Block(m, func() {
|
|
||||||
isOpen = m._isOpen
|
|
||||||
})
|
|
||||||
return isOpen
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the multicast module.
|
// Stop stops the multicast module.
|
||||||
|
@ -133,8 +136,10 @@ func (m *Multicast) Stop() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Multicast) _stop() error {
|
func (m *Multicast) _stop() error {
|
||||||
|
if !m.running.CompareAndSwap(true, false) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
m.log.Infoln("Stopping multicast module")
|
m.log.Infoln("Stopping multicast module")
|
||||||
m._isOpen = false
|
|
||||||
if m.sock != nil {
|
if m.sock != nil {
|
||||||
m.sock.Close()
|
m.sock.Close()
|
||||||
}
|
}
|
||||||
|
@ -144,14 +149,22 @@ func (m *Multicast) _stop() error {
|
||||||
func (m *Multicast) _updateInterfaces() {
|
func (m *Multicast) _updateInterfaces() {
|
||||||
interfaces := m._getAllowedInterfaces()
|
interfaces := m._getAllowedInterfaces()
|
||||||
for name, info := range interfaces {
|
for name, info := range interfaces {
|
||||||
addrs, err := info.iface.Addrs()
|
// 'anet' package is used here to avoid https://github.com/golang/go/issues/40569
|
||||||
|
addrs, err := anet.InterfaceAddrsByInterface(&info.iface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.log.Warnf("Failed up get addresses for interface %s: %s", name, err)
|
m.log.Warnf("Failed up get addresses for interface %s: %s", name, err)
|
||||||
delete(interfaces, name)
|
delete(interfaces, name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
info.addrs = addrs
|
for _, addr := range addrs {
|
||||||
|
addrIP, _, err := net.ParseCIDR(addr.String())
|
||||||
|
if err != nil || addrIP.To4() != nil || !addrIP.IsLinkLocalUnicast() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
info.addrs = append(info.addrs, addr)
|
||||||
|
}
|
||||||
interfaces[name] = info
|
interfaces[name] = info
|
||||||
|
m.log.Debugf("Discovered addresses for interface %s: %s", name, addrs)
|
||||||
}
|
}
|
||||||
m._interfaces = interfaces
|
m._interfaces = interfaces
|
||||||
}
|
}
|
||||||
|
@ -170,17 +183,21 @@ func (m *Multicast) Interfaces() map[string]net.Interface {
|
||||||
func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo {
|
func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo {
|
||||||
interfaces := make(map[string]*interfaceInfo)
|
interfaces := make(map[string]*interfaceInfo)
|
||||||
// Ask the system for network interfaces
|
// Ask the system for network interfaces
|
||||||
allifaces, err := net.Interfaces()
|
// 'anet' package is used here to avoid https://github.com/golang/go/issues/40569
|
||||||
|
allifaces, err := anet.Interfaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Don't panic, since this may be from e.g. too many open files (from too much connection spam)
|
// Don't panic, since this may be from e.g. too many open files (from too much connection spam)
|
||||||
// TODO? log something
|
m.log.Debugf("Failed to get interfaces: %s", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Work out which interfaces to announce on
|
// Work out which interfaces to announce on
|
||||||
|
pk := m.core.PublicKey()
|
||||||
for _, iface := range allifaces {
|
for _, iface := range allifaces {
|
||||||
switch {
|
switch {
|
||||||
case iface.Flags&net.FlagUp == 0:
|
case iface.Flags&net.FlagUp == 0:
|
||||||
continue // Ignore interfaces that are down
|
continue // Ignore interfaces that are down
|
||||||
|
case iface.Flags&net.FlagRunning == 0:
|
||||||
|
continue // Ignore interfaces that are not running
|
||||||
case iface.Flags&net.FlagMulticast == 0:
|
case iface.Flags&net.FlagMulticast == 0:
|
||||||
continue // Ignore non-multicast interfaces
|
continue // Ignore non-multicast interfaces
|
||||||
case iface.Flags&net.FlagPointToPoint != 0:
|
case iface.Flags&net.FlagPointToPoint != 0:
|
||||||
|
@ -195,12 +212,23 @@ func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo {
|
||||||
if !ifcfg.Regex.MatchString(iface.Name) {
|
if !ifcfg.Regex.MatchString(iface.Name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
hasher, err := blake2b.New512([]byte(ifcfg.Password))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n, err := hasher.Write(pk); err != nil {
|
||||||
|
continue
|
||||||
|
} else if n != ed25519.PublicKeySize {
|
||||||
|
continue
|
||||||
|
}
|
||||||
interfaces[iface.Name] = &interfaceInfo{
|
interfaces[iface.Name] = &interfaceInfo{
|
||||||
iface: iface,
|
iface: iface,
|
||||||
beacon: ifcfg.Beacon,
|
beacon: ifcfg.Beacon,
|
||||||
listen: ifcfg.Listen,
|
listen: ifcfg.Listen,
|
||||||
port: ifcfg.Port,
|
port: ifcfg.Port,
|
||||||
priority: ifcfg.Priority,
|
priority: ifcfg.Priority,
|
||||||
|
password: []byte(ifcfg.Password),
|
||||||
|
hash: hasher.Sum(nil),
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -218,7 +246,7 @@ func (m *Multicast) AnnounceNow() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Multicast) _announce() {
|
func (m *Multicast) _announce() {
|
||||||
if !m._isOpen {
|
if !m.running.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m._updateInterfaces()
|
m._updateInterfaces()
|
||||||
|
@ -235,7 +263,7 @@ func (m *Multicast) _announce() {
|
||||||
for name, info := range m._listeners {
|
for name, info := range m._listeners {
|
||||||
// Prepare our stop function!
|
// Prepare our stop function!
|
||||||
stop := func() {
|
stop := func() {
|
||||||
info.listener.Close()
|
info.listener.Cancel()
|
||||||
delete(m._listeners, name)
|
delete(m._listeners, name)
|
||||||
m.log.Debugln("No longer multicasting on", name)
|
m.log.Debugln("No longer multicasting on", name)
|
||||||
}
|
}
|
||||||
|
@ -248,7 +276,7 @@ func (m *Multicast) _announce() {
|
||||||
// It's possible that the link-local listener address has changed so if
|
// It's possible that the link-local listener address has changed so if
|
||||||
// that is the case then we should clean up the interface listener
|
// that is the case then we should clean up the interface listener
|
||||||
found := false
|
found := false
|
||||||
listenaddr, err := net.ResolveTCPAddr("tcp6", info.listener.Listener.Addr().String())
|
listenaddr, err := net.ResolveTCPAddr("tcp6", info.listener.Addr().String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stop()
|
stop()
|
||||||
continue
|
continue
|
||||||
|
@ -277,13 +305,9 @@ func (m *Multicast) _announce() {
|
||||||
for _, info := range m._interfaces {
|
for _, info := range m._interfaces {
|
||||||
iface := info.iface
|
iface := info.iface
|
||||||
for _, addr := range info.addrs {
|
for _, addr := range info.addrs {
|
||||||
addrIP, _, _ := net.ParseCIDR(addr.String())
|
addrIP, _, err := net.ParseCIDR(addr.String())
|
||||||
// Ignore IPv4 addresses
|
// Ignore IPv4 addresses or non-link-local addresses
|
||||||
if addrIP.To4() != nil {
|
if err != nil || addrIP.To4() != nil || !addrIP.IsLinkLocalUnicast() {
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Ignore non-link-local addresses
|
|
||||||
if !addrIP.IsLinkLocalUnicast() {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if info.listen {
|
if info.listen {
|
||||||
|
@ -295,14 +319,17 @@ func (m *Multicast) _announce() {
|
||||||
}
|
}
|
||||||
// Try and see if we already have a TCP listener for this interface
|
// Try and see if we already have a TCP listener for this interface
|
||||||
var linfo *listenerInfo
|
var linfo *listenerInfo
|
||||||
if nfo, ok := m._listeners[iface.Name]; !ok || nfo.listener.Listener == nil {
|
if _, ok := m._listeners[iface.Name]; !ok {
|
||||||
// No listener was found - let's create one
|
// No listener was found - let's create one
|
||||||
urlString := fmt.Sprintf("tls://[%s]:%d", addrIP, info.port)
|
v := &url.Values{}
|
||||||
u, err := url.Parse(urlString)
|
v.Add("priority", fmt.Sprintf("%d", info.priority))
|
||||||
if err != nil {
|
v.Add("password", string(info.password))
|
||||||
panic(err)
|
u := &url.URL{
|
||||||
|
Scheme: "tls",
|
||||||
|
Host: net.JoinHostPort(addrIP.String(), fmt.Sprintf("%d", info.port)),
|
||||||
|
RawQuery: v.Encode(),
|
||||||
}
|
}
|
||||||
if li, err := m.core.Listen(u, iface.Name); err == nil {
|
if li, err := m.core.ListenLocal(u, iface.Name); err == nil {
|
||||||
m.log.Debugln("Started multicasting on", iface.Name)
|
m.log.Debugln("Started multicasting on", iface.Name)
|
||||||
// Store the listener so that we can stop it later if needed
|
// Store the listener so that we can stop it later if needed
|
||||||
linfo = &listenerInfo{listener: li, time: time.Now(), port: info.port}
|
linfo = &listenerInfo{listener: li, time: time.Now(), port: info.port}
|
||||||
|
@ -321,25 +348,31 @@ func (m *Multicast) _announce() {
|
||||||
if time.Since(linfo.time) < linfo.interval {
|
if time.Since(linfo.time) < linfo.interval {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Get the listener details and construct the multicast beacon
|
addr := linfo.listener.Addr().(*net.TCPAddr)
|
||||||
lladdr := linfo.listener.Listener.Addr().String()
|
adv := multicastAdvertisement{
|
||||||
if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil {
|
MajorVersion: core.ProtocolVersionMajor,
|
||||||
a.Zone = ""
|
MinorVersion: core.ProtocolVersionMinor,
|
||||||
destAddr.Zone = iface.Name
|
PublicKey: m.core.PublicKey(),
|
||||||
msg := append([]byte(nil), m.core.GetSelf().Key...)
|
Port: uint16(addr.Port),
|
||||||
msg = append(msg, a.IP...)
|
Hash: info.hash,
|
||||||
pbs := make([]byte, 2)
|
}
|
||||||
binary.BigEndian.PutUint16(pbs, uint16(a.Port))
|
msg, err := adv.MarshalBinary()
|
||||||
msg = append(msg, pbs...)
|
if err != nil {
|
||||||
_, _ = m.sock.WriteTo(msg, nil, destAddr)
|
continue
|
||||||
|
}
|
||||||
|
destAddr.Zone = iface.Name
|
||||||
|
if _, err = m.sock.WriteTo(msg, nil, destAddr); err != nil {
|
||||||
|
m.log.Warn("Failed to send multicast beacon:", err)
|
||||||
}
|
}
|
||||||
if linfo.interval.Seconds() < 15 {
|
if linfo.interval.Seconds() < 15 {
|
||||||
linfo.interval += time.Second
|
linfo.interval += time.Second
|
||||||
}
|
}
|
||||||
|
linfo.time = time.Now()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m._timer = time.AfterFunc(time.Second, func() {
|
annInterval := time.Second + time.Microsecond*(time.Duration(rand.Intn(1048576))) // Randomize delay
|
||||||
|
m._timer = time.AfterFunc(annInterval, func() {
|
||||||
m.Act(nil, m._announce)
|
m.Act(nil, m._announce)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -350,8 +383,12 @@ func (m *Multicast) listen() {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
bs := make([]byte, 2048)
|
bs := make([]byte, 2048)
|
||||||
|
hb := make([]byte, 0, blake2b.Size) // Reused to reduce hash allocations
|
||||||
for {
|
for {
|
||||||
nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs)
|
if !m.running.Load() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, rcm, fromAddr, err := m.sock.ReadFrom(bs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !m.IsStarted() {
|
if !m.IsStarted() {
|
||||||
return
|
return
|
||||||
|
@ -369,40 +406,45 @@ func (m *Multicast) listen() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if nBytes < ed25519.PublicKeySize {
|
var adv multicastAdvertisement
|
||||||
|
if err := adv.UnmarshalBinary(bs[:n]); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var key ed25519.PublicKey
|
switch {
|
||||||
key = append(key, bs[:ed25519.PublicKeySize]...)
|
case adv.MajorVersion != core.ProtocolVersionMajor:
|
||||||
if bytes.Equal(key, m.core.GetSelf().Key) {
|
continue
|
||||||
continue // don't bother trying to peer with self
|
case adv.MinorVersion != core.ProtocolVersionMinor:
|
||||||
}
|
continue
|
||||||
begin := ed25519.PublicKeySize
|
case adv.PublicKey.Equal(m.core.PublicKey()):
|
||||||
end := nBytes - 2
|
|
||||||
if end <= begin {
|
|
||||||
continue // malformed address
|
|
||||||
}
|
|
||||||
ip := bs[begin:end]
|
|
||||||
port := binary.BigEndian.Uint16(bs[end:nBytes])
|
|
||||||
anAddr := net.TCPAddr{IP: ip, Port: int(port)}
|
|
||||||
addr, err := net.ResolveTCPAddr("tcp6", anAddr.String())
|
|
||||||
if err != nil {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
from := fromAddr.(*net.UDPAddr)
|
from := fromAddr.(*net.UDPAddr)
|
||||||
if !from.IP.Equal(addr.IP) {
|
from.Port = int(adv.Port)
|
||||||
continue
|
|
||||||
}
|
|
||||||
var interfaces map[string]*interfaceInfo
|
var interfaces map[string]*interfaceInfo
|
||||||
phony.Block(m, func() {
|
phony.Block(m, func() {
|
||||||
interfaces = m._interfaces
|
interfaces = m._interfaces
|
||||||
})
|
})
|
||||||
if info, ok := interfaces[from.Zone]; ok && info.listen {
|
if info, ok := interfaces[from.Zone]; ok && info.listen {
|
||||||
addr.Zone = ""
|
hasher, err := blake2b.New512(info.password)
|
||||||
pin := fmt.Sprintf("/?key=%s&priority=%d", hex.EncodeToString(key), info.priority)
|
|
||||||
u, err := url.Parse("tls://" + addr.String() + pin)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.log.Debugln("Call from multicast failed, parse error:", addr.String(), err)
|
continue
|
||||||
|
}
|
||||||
|
if n, err := hasher.Write(adv.PublicKey); err != nil {
|
||||||
|
continue
|
||||||
|
} else if n != ed25519.PublicKeySize {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(hasher.Sum(hb[:0]), adv.Hash) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v := &url.Values{}
|
||||||
|
v.Add("key", hex.EncodeToString(adv.PublicKey))
|
||||||
|
v.Add("priority", fmt.Sprintf("%d", info.priority))
|
||||||
|
v.Add("password", string(info.password))
|
||||||
|
u := &url.URL{
|
||||||
|
Scheme: "tls",
|
||||||
|
Host: from.String(),
|
||||||
|
RawQuery: v.Encode(),
|
||||||
}
|
}
|
||||||
if err := m.core.CallPeer(u, from.Zone); err != nil {
|
if err := m.core.CallPeer(u, from.Zone); err != nil {
|
||||||
m.log.Debugln("Call from multicast failed:", err)
|
m.log.Debugln("Call from multicast failed:", err)
|
||||||
|
|
|
@ -31,7 +31,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *Multicast) _multicastStarted() {
|
func (m *Multicast) _multicastStarted() {
|
||||||
if !m._isOpen {
|
if !m.running.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
C.StopAWDLBrowsing()
|
C.StopAWDLBrowsing()
|
||||||
|
|
|
@ -21,6 +21,7 @@ type MulticastInterface struct {
|
||||||
Listen bool
|
Listen bool
|
||||||
Port uint16
|
Port uint16
|
||||||
Priority uint8
|
Priority uint8
|
||||||
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
type GroupAddress string
|
type GroupAddress string
|
||||||
|
|
|
@ -1,42 +1,70 @@
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
const TUN_OFFSET_BYTES = 4
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
wgtun "golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TUN_OFFSET_BYTES = 80 // sizeof(virtio_net_hdr)
|
||||||
|
|
||||||
func (tun *TunAdapter) read() {
|
func (tun *TunAdapter) read() {
|
||||||
var buf [TUN_OFFSET_BYTES + 65535]byte
|
vs := tun.iface.BatchSize()
|
||||||
|
bufs := make([][]byte, vs)
|
||||||
|
sizes := make([]int, vs)
|
||||||
|
for i := range bufs {
|
||||||
|
bufs[i] = make([]byte, TUN_OFFSET_BYTES+65535)
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
n, err := tun.iface.Read(buf[:], TUN_OFFSET_BYTES)
|
n, err := tun.iface.Read(bufs, sizes, TUN_OFFSET_BYTES)
|
||||||
if n <= TUN_OFFSET_BYTES || err != nil {
|
if err != nil {
|
||||||
tun.log.Errorln("Error reading TUN:", err)
|
if errors.Is(err, wgtun.ErrTooManySegments) {
|
||||||
ferr := tun.iface.Flush()
|
tun.log.Debugln("TUN segments dropped: %v", err)
|
||||||
if ferr != nil {
|
continue
|
||||||
tun.log.Errorln("Unable to flush packets:", ferr)
|
|
||||||
}
|
}
|
||||||
|
tun.log.Errorln("Error reading TUN:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
begin := TUN_OFFSET_BYTES
|
for i, b := range bufs[:n] {
|
||||||
end := begin + n
|
if _, err := tun.rwc.Write(b[TUN_OFFSET_BYTES : TUN_OFFSET_BYTES+sizes[i]]); err != nil {
|
||||||
bs := buf[begin:end]
|
tun.log.Debugln("Unable to send packet:", err)
|
||||||
if _, err := tun.rwc.Write(bs); err != nil {
|
}
|
||||||
tun.log.Debugln("Unable to send packet:", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *TunAdapter) write() {
|
func (tun *TunAdapter) queue() {
|
||||||
var buf [TUN_OFFSET_BYTES + 65535]byte
|
|
||||||
for {
|
for {
|
||||||
bs := buf[TUN_OFFSET_BYTES:]
|
p := bufPool.Get().([]byte)[:bufPoolSize]
|
||||||
n, err := tun.rwc.Read(bs)
|
n, err := tun.rwc.Read(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tun.log.Errorln("Exiting tun writer due to core read error:", err)
|
tun.log.Errorln("Exiting TUN writer due to core read error:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
tun.ch <- p[:n]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tun *TunAdapter) write() {
|
||||||
|
vs := cap(tun.ch)
|
||||||
|
bufs := make([][]byte, vs)
|
||||||
|
for i := range bufs {
|
||||||
|
bufs[i] = make([]byte, TUN_OFFSET_BYTES+65535)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
n := len(tun.ch)
|
||||||
|
if n == 0 {
|
||||||
|
n = 1 // Nothing queued up yet, wait for it instead
|
||||||
|
}
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
msg := <-tun.ch
|
||||||
|
bufs[i] = append(bufs[i][:TUN_OFFSET_BYTES], msg...)
|
||||||
|
bufPool.Put(msg) // nolint:staticcheck
|
||||||
|
}
|
||||||
if !tun.isEnabled {
|
if !tun.isEnabled {
|
||||||
continue // Nothing to do, the tun isn't enabled
|
continue // Nothing to do, the tun isn't enabled
|
||||||
}
|
}
|
||||||
bs = buf[:TUN_OFFSET_BYTES+n]
|
if _, err := tun.iface.Write(bufs[:n], TUN_OFFSET_BYTES); err != nil {
|
||||||
if _, err = tun.iface.Write(bs, TUN_OFFSET_BYTES); err != nil {
|
|
||||||
tun.Act(nil, func() {
|
tun.Act(nil, func() {
|
||||||
if !tun.isOpen {
|
if !tun.isOpen {
|
||||||
tun.log.Errorln("TUN iface write error:", err)
|
tun.log.Errorln("TUN iface write error:", err)
|
||||||
|
|
|
@ -6,6 +6,8 @@ func (m *TunAdapter) _applyOption(opt SetupOption) {
|
||||||
m.config.name = v
|
m.config.name = v
|
||||||
case InterfaceMTU:
|
case InterfaceMTU:
|
||||||
m.config.mtu = v
|
m.config.mtu = v
|
||||||
|
case FileDescriptor:
|
||||||
|
m.config.fd = int32(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +17,8 @@ type SetupOption interface {
|
||||||
|
|
||||||
type InterfaceName string
|
type InterfaceName string
|
||||||
type InterfaceMTU uint64
|
type InterfaceMTU uint64
|
||||||
|
type FileDescriptor int32
|
||||||
|
|
||||||
func (a InterfaceName) isSetupOption() {}
|
func (a InterfaceName) isSetupOption() {}
|
||||||
func (a InterfaceMTU) isSetupOption() {}
|
func (a InterfaceMTU) isSetupOption() {}
|
||||||
|
func (a FileDescriptor) isSetupOption() {}
|
||||||
|
|
|
@ -8,42 +8,52 @@ package tun
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/Arceliar/phony"
|
"github.com/Arceliar/phony"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
wgtun "golang.zx2c4.com/wireguard/tun"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||||
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MTU uint16
|
type MTU uint16
|
||||||
|
|
||||||
|
type ReadWriteCloser interface {
|
||||||
|
io.ReadWriteCloser
|
||||||
|
Address() address.Address
|
||||||
|
Subnet() address.Subnet
|
||||||
|
MaxMTU() uint64
|
||||||
|
SetMTU(uint64)
|
||||||
|
}
|
||||||
|
|
||||||
// TunAdapter represents a running TUN interface and extends the
|
// TunAdapter represents a running TUN interface and extends the
|
||||||
// yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you
|
// yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you
|
||||||
// should pass this object to the yggdrasil.SetRouterAdapter() function before
|
// should pass this object to the yggdrasil.SetRouterAdapter() function before
|
||||||
// calling yggdrasil.Start().
|
// calling yggdrasil.Start().
|
||||||
type TunAdapter struct {
|
type TunAdapter struct {
|
||||||
rwc *ipv6rwc.ReadWriteCloser
|
rwc ReadWriteCloser
|
||||||
log core.Logger
|
log core.Logger
|
||||||
addr address.Address
|
addr address.Address
|
||||||
subnet address.Subnet
|
subnet address.Subnet
|
||||||
mtu uint64
|
mtu uint64
|
||||||
iface tun.Device
|
iface wgtun.Device
|
||||||
phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below
|
phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below
|
||||||
//mutex sync.RWMutex // Protects the below
|
isOpen bool
|
||||||
isOpen bool
|
isEnabled bool // Used by the writer to drop sessionTraffic if not enabled
|
||||||
isEnabled bool // Used by the writer to drop sessionTraffic if not enabled
|
config struct {
|
||||||
config struct {
|
fd int32
|
||||||
name InterfaceName
|
name InterfaceName
|
||||||
mtu InterfaceMTU
|
mtu InterfaceMTU
|
||||||
}
|
}
|
||||||
|
ch chan []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the maximum supported MTU for the platform based on the defaults in
|
// Gets the maximum supported MTU for the platform based on the defaults in
|
||||||
// defaults.GetDefaults().
|
// config.GetDefaults().
|
||||||
func getSupportedMTU(mtu uint64) uint64 {
|
func getSupportedMTU(mtu uint64) uint64 {
|
||||||
if mtu < 1280 {
|
if mtu < 1280 {
|
||||||
return 1280
|
return 1280
|
||||||
|
@ -72,25 +82,25 @@ func (tun *TunAdapter) MTU() uint64 {
|
||||||
|
|
||||||
// DefaultName gets the default TUN interface name for your platform.
|
// DefaultName gets the default TUN interface name for your platform.
|
||||||
func DefaultName() string {
|
func DefaultName() string {
|
||||||
return defaults.GetDefaults().DefaultIfName
|
return config.GetDefaults().DefaultIfName
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultMTU gets the default TUN interface MTU for your platform. This can
|
// DefaultMTU gets the default TUN interface MTU for your platform. This can
|
||||||
// be as high as MaximumMTU(), depending on platform, but is never lower than 1280.
|
// be as high as MaximumMTU(), depending on platform, but is never lower than 1280.
|
||||||
func DefaultMTU() uint64 {
|
func DefaultMTU() uint64 {
|
||||||
return defaults.GetDefaults().DefaultIfMTU
|
return config.GetDefaults().DefaultIfMTU
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaximumMTU returns the maximum supported TUN interface MTU for your
|
// MaximumMTU returns the maximum supported TUN interface MTU for your
|
||||||
// platform. This can be as high as 65535, depending on platform, but is never
|
// platform. This can be as high as 65535, depending on platform, but is never
|
||||||
// lower than 1280.
|
// lower than 1280.
|
||||||
func MaximumMTU() uint64 {
|
func MaximumMTU() uint64 {
|
||||||
return defaults.GetDefaults().MaximumIfMTU
|
return config.GetDefaults().MaximumIfMTU
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initialises the TUN module. You must have acquired a Listener from
|
// Init initialises the TUN module. You must have acquired a Listener from
|
||||||
// the Yggdrasil core before this point and it must not be in use elsewhere.
|
// the Yggdrasil core before this point and it must not be in use elsewhere.
|
||||||
func New(rwc *ipv6rwc.ReadWriteCloser, log core.Logger, opts ...SetupOption) (*TunAdapter, error) {
|
func New(rwc ReadWriteCloser, log core.Logger, opts ...SetupOption) (*TunAdapter, error) {
|
||||||
tun := &TunAdapter{
|
tun := &TunAdapter{
|
||||||
rwc: rwc,
|
rwc: rwc,
|
||||||
log: log,
|
log: log,
|
||||||
|
@ -107,10 +117,15 @@ func (tun *TunAdapter) _start() error {
|
||||||
}
|
}
|
||||||
tun.addr = tun.rwc.Address()
|
tun.addr = tun.rwc.Address()
|
||||||
tun.subnet = tun.rwc.Subnet()
|
tun.subnet = tun.rwc.Subnet()
|
||||||
addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1)
|
prefix := address.GetPrefix()
|
||||||
|
var addr string
|
||||||
|
if tun.addr.IsValid() {
|
||||||
|
addr = fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(prefix[:])-1)
|
||||||
|
}
|
||||||
if tun.config.name == "none" || tun.config.name == "dummy" {
|
if tun.config.name == "none" || tun.config.name == "dummy" {
|
||||||
tun.log.Debugln("Not starting TUN as ifname is none or dummy")
|
tun.log.Debugln("Not starting TUN as ifname is none or dummy")
|
||||||
tun.isEnabled = false
|
tun.isEnabled = false
|
||||||
|
go tun.queue()
|
||||||
go tun.write()
|
go tun.write()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -118,7 +133,13 @@ func (tun *TunAdapter) _start() error {
|
||||||
if tun.rwc.MaxMTU() < mtu {
|
if tun.rwc.MaxMTU() < mtu {
|
||||||
mtu = tun.rwc.MaxMTU()
|
mtu = tun.rwc.MaxMTU()
|
||||||
}
|
}
|
||||||
if err := tun.setup(string(tun.config.name), addr, mtu); err != nil {
|
var err error
|
||||||
|
if tun.config.fd > 0 {
|
||||||
|
err = tun.setupFD(tun.config.fd, addr, mtu)
|
||||||
|
} else {
|
||||||
|
err = tun.setup(string(tun.config.name), addr, mtu)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if tun.MTU() != mtu {
|
if tun.MTU() != mtu {
|
||||||
|
@ -127,6 +148,8 @@ func (tun *TunAdapter) _start() error {
|
||||||
tun.rwc.SetMTU(tun.MTU())
|
tun.rwc.SetMTU(tun.MTU())
|
||||||
tun.isOpen = true
|
tun.isOpen = true
|
||||||
tun.isEnabled = true
|
tun.isEnabled = true
|
||||||
|
tun.ch = make(chan []byte, tun.iface.BatchSize())
|
||||||
|
go tun.queue()
|
||||||
go tun.read()
|
go tun.read()
|
||||||
go tun.write()
|
go tun.write()
|
||||||
return nil
|
return nil
|
||||||
|
@ -160,3 +183,12 @@ func (tun *TunAdapter) _stop() error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bufPoolSize = TUN_OFFSET_BYTES + 65535
|
||||||
|
|
||||||
|
var bufPool = sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
b := [bufPoolSize]byte{}
|
||||||
|
return b[:]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//go:build !mobile
|
//go:build darwin || ios
|
||||||
// +build !mobile
|
// +build darwin ios
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
@ -23,7 +25,7 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
||||||
}
|
}
|
||||||
iface, err := wgtun.CreateTUN(ifname, int(mtu))
|
iface, err := wgtun.CreateTUN(ifname, int(mtu))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return fmt.Errorf("failed to create TUN: %w", err)
|
||||||
}
|
}
|
||||||
tun.iface = iface
|
tun.iface = iface
|
||||||
if m, err := iface.MTU(); err == nil {
|
if m, err := iface.MTU(); err == nil {
|
||||||
|
@ -31,7 +33,35 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
||||||
} else {
|
} else {
|
||||||
tun.mtu = 0
|
tun.mtu = 0
|
||||||
}
|
}
|
||||||
return tun.setupAddress(addr)
|
if addr != "" {
|
||||||
|
return tun.setupAddress(addr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configures the "utun" adapter from an existing file descriptor.
|
||||||
|
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
|
||||||
|
dfd, err := unix.Dup(int(fd))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to duplicate FD: %w", err)
|
||||||
|
}
|
||||||
|
err = unix.SetNonblock(dfd, true)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(dfd)
|
||||||
|
return fmt.Errorf("failed to set FD as non-blocking: %w", err)
|
||||||
|
}
|
||||||
|
iface, err := wgtun.CreateTUNFromFile(os.NewFile(uintptr(dfd), "/dev/tun"), 0)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(dfd)
|
||||||
|
return fmt.Errorf("failed to create TUN from FD: %w", err)
|
||||||
|
}
|
||||||
|
tun.iface = iface
|
||||||
|
if m, err := iface.MTU(); err == nil {
|
||||||
|
tun.mtu = getSupportedMTU(uint64(m))
|
||||||
|
} else {
|
||||||
|
tun.mtu = 0
|
||||||
|
}
|
||||||
|
return nil // tun.setupAddress(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -81,8 +111,8 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil {
|
if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil {
|
||||||
tun.log.Printf("Create AF_SYSTEM socket failed: %v.", err)
|
tun.log.Errorf("Create AF_SYSTEM socket failed: %v.", err)
|
||||||
return err
|
return fmt.Errorf("failed to open AF_SYSTEM: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ar in6_aliasreq
|
var ar in6_aliasreq
|
||||||
|
@ -117,16 +147,16 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
||||||
tun.log.Infof("Interface IPv6: %s", addr)
|
tun.log.Infof("Interface IPv6: %s", addr)
|
||||||
tun.log.Infof("Interface MTU: %d", ir.ifru_mtu)
|
tun.log.Infof("Interface MTU: %d", ir.ifru_mtu)
|
||||||
|
|
||||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 {
|
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { // nolint:staticcheck
|
||||||
err = errno
|
err = errno
|
||||||
tun.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno)
|
tun.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno)
|
||||||
return err
|
return fmt.Errorf("failed to call SIOCAIFADDR_IN6: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
|
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { // nolint:staticcheck
|
||||||
err = errno
|
err = errno
|
||||||
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
|
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
|
||||||
return err
|
return fmt.Errorf("failed to call SIOCSIFMTU: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
//go:build openbsd || freebsd
|
//go:build freebsd
|
||||||
// +build openbsd freebsd
|
// +build freebsd
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -53,11 +54,6 @@ struct in6_ifreq {
|
||||||
290 };
|
290 };
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type in6_ifreq_mtu struct {
|
|
||||||
ifr_name [syscall.IFNAMSIZ]byte
|
|
||||||
ifru_mtu int
|
|
||||||
}
|
|
||||||
|
|
||||||
type in6_ifreq_addr struct {
|
type in6_ifreq_addr struct {
|
||||||
ifr_name [syscall.IFNAMSIZ]byte
|
ifr_name [syscall.IFNAMSIZ]byte
|
||||||
ifru_addr sockaddr_in6
|
ifru_addr sockaddr_in6
|
||||||
|
@ -77,7 +73,7 @@ type in6_ifreq_lifetime struct {
|
||||||
func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
||||||
iface, err := wgtun.CreateTUN(ifname, int(mtu))
|
iface, err := wgtun.CreateTUN(ifname, int(mtu))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return fmt.Errorf("failed to create TUN: %w", err)
|
||||||
}
|
}
|
||||||
tun.iface = iface
|
tun.iface = iface
|
||||||
if mtu, err := iface.MTU(); err == nil {
|
if mtu, err := iface.MTU(); err == nil {
|
||||||
|
@ -85,7 +81,15 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
||||||
} else {
|
} else {
|
||||||
tun.mtu = 0
|
tun.mtu = 0
|
||||||
}
|
}
|
||||||
return tun.setupAddress(addr)
|
if addr != "" {
|
||||||
|
return tun.setupAddress(addr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configures the "utun" adapter from an existing file descriptor.
|
||||||
|
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
|
||||||
|
return fmt.Errorf("setup via FD not supported on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *TunAdapter) setupAddress(addr string) error {
|
func (tun *TunAdapter) setupAddress(addr string) error {
|
||||||
|
@ -103,26 +107,6 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
||||||
tun.log.Infof("Interface IPv6: %s", addr)
|
tun.log.Infof("Interface IPv6: %s", addr)
|
||||||
tun.log.Infof("Interface MTU: %d", tun.mtu)
|
tun.log.Infof("Interface MTU: %d", tun.mtu)
|
||||||
|
|
||||||
// Create the MTU request
|
|
||||||
var ir in6_ifreq_mtu
|
|
||||||
copy(ir.ifr_name[:], tun.Name())
|
|
||||||
ir.ifru_mtu = int(tun.mtu)
|
|
||||||
|
|
||||||
// Set the MTU
|
|
||||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
|
|
||||||
err = errno
|
|
||||||
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
|
|
||||||
|
|
||||||
// Fall back to ifconfig to set the MTU
|
|
||||||
cmd := exec.Command("ifconfig", tun.Name(), "mtu", string(tun.mtu))
|
|
||||||
tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
|
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err)
|
|
||||||
tun.log.Traceln(string(output))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the address request
|
// Create the address request
|
||||||
// FIXME: I don't work!
|
// FIXME: I don't work!
|
||||||
var ar in6_ifreq_addr
|
var ar in6_ifreq_addr
|
|
@ -1,11 +1,13 @@
|
||||||
//go:build !mobile
|
//go:build linux || android
|
||||||
// +build !mobile
|
// +build linux android
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
// The linux platform specific tun parts
|
// The linux platform specific tun parts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
wgtun "golang.zx2c4.com/wireguard/tun"
|
wgtun "golang.zx2c4.com/wireguard/tun"
|
||||||
)
|
)
|
||||||
|
@ -17,7 +19,7 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
||||||
}
|
}
|
||||||
iface, err := wgtun.CreateTUN(ifname, int(mtu))
|
iface, err := wgtun.CreateTUN(ifname, int(mtu))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return fmt.Errorf("failed to create TUN: %w", err)
|
||||||
}
|
}
|
||||||
tun.iface = iface
|
tun.iface = iface
|
||||||
if mtu, err := iface.MTU(); err == nil {
|
if mtu, err := iface.MTU(); err == nil {
|
||||||
|
@ -25,7 +27,15 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
||||||
} else {
|
} else {
|
||||||
tun.mtu = 0
|
tun.mtu = 0
|
||||||
}
|
}
|
||||||
return tun.setupAddress(addr)
|
if addr != "" {
|
||||||
|
return tun.setupAddress(addr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configures the "utun" adapter from an existing file descriptor.
|
||||||
|
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
|
||||||
|
return fmt.Errorf("setup via FD not supported on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configures the TUN adapter with the correct IPv6 address and MTU. Netlink
|
// Configures the TUN adapter with the correct IPv6 address and MTU. Netlink
|
||||||
|
@ -35,20 +45,20 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
||||||
func (tun *TunAdapter) setupAddress(addr string) error {
|
func (tun *TunAdapter) setupAddress(addr string) error {
|
||||||
nladdr, err := netlink.ParseAddr(addr)
|
nladdr, err := netlink.ParseAddr(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("couldn't parse address %q: %w", addr, err)
|
||||||
}
|
}
|
||||||
nlintf, err := netlink.LinkByName(tun.Name())
|
nlintf, err := netlink.LinkByName(tun.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to find link by name: %w", err)
|
||||||
}
|
}
|
||||||
if err := netlink.AddrAdd(nlintf, nladdr); err != nil {
|
if err := netlink.AddrAdd(nlintf, nladdr); err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to add address to link: %w", err)
|
||||||
}
|
}
|
||||||
if err := netlink.LinkSetMTU(nlintf, int(tun.mtu)); err != nil {
|
if err := netlink.LinkSetMTU(nlintf, int(tun.mtu)); err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to set link MTU: %w", err)
|
||||||
}
|
}
|
||||||
if err := netlink.LinkSetUp(nlintf); err != nil {
|
if err := netlink.LinkSetUp(nlintf); err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to bring link up: %w", err)
|
||||||
}
|
}
|
||||||
// Friendly output
|
// Friendly output
|
||||||
tun.log.Infof("Interface name: %s", tun.Name())
|
tun.log.Infof("Interface name: %s", tun.Name())
|
||||||
|
|
122
src/tun/tun_openbsd.go
Normal file
122
src/tun/tun_openbsd.go
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
//go:build openbsd
|
||||||
|
// +build openbsd
|
||||||
|
|
||||||
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
wgtun "golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SIOCAIFADDR_IN6 = 0x8080691a
|
||||||
|
ND6_INFINITE_LIFETIME = 0xffffffff
|
||||||
|
)
|
||||||
|
|
||||||
|
type in6_addrlifetime struct {
|
||||||
|
ia6t_expire int64
|
||||||
|
ia6t_preferred int64
|
||||||
|
ia6t_vltime uint32
|
||||||
|
ia6t_pltime uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match types from the net package, effectively being [16]byte for IPv6 addresses.
|
||||||
|
type in6_addr [16]uint8
|
||||||
|
|
||||||
|
type sockaddr_in6 struct {
|
||||||
|
sin6_len uint8
|
||||||
|
sin6_family uint8
|
||||||
|
sin6_port uint16
|
||||||
|
sin6_flowinfo uint32
|
||||||
|
sin6_addr in6_addr
|
||||||
|
sin6_scope_id uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sa6 *sockaddr_in6) setSockaddr(addr [/*16*/]byte /* net.IP or net.IPMask */) {
|
||||||
|
sa6.sin6_len = uint8(unsafe.Sizeof(*sa6))
|
||||||
|
sa6.sin6_family = unix.AF_INET6
|
||||||
|
|
||||||
|
for i := range sa6.sin6_addr {
|
||||||
|
sa6.sin6_addr[i] = addr[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type in6_aliasreq struct {
|
||||||
|
ifra_name [syscall.IFNAMSIZ]byte
|
||||||
|
ifra_addr sockaddr_in6
|
||||||
|
ifra_dstaddr sockaddr_in6
|
||||||
|
ifra_prefixmask sockaddr_in6
|
||||||
|
ifra_flags int32
|
||||||
|
ifra_lifetime in6_addrlifetime
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configures the TUN adapter with the correct IPv6 address and MTU.
|
||||||
|
func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
||||||
|
iface, err := wgtun.CreateTUN(ifname, int(mtu))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create TUN: %w", err)
|
||||||
|
}
|
||||||
|
tun.iface = iface
|
||||||
|
if mtu, err := iface.MTU(); err == nil {
|
||||||
|
tun.mtu = getSupportedMTU(uint64(mtu))
|
||||||
|
} else {
|
||||||
|
tun.mtu = 0
|
||||||
|
}
|
||||||
|
if addr != "" {
|
||||||
|
return tun.setupAddress(addr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configures the "utun" adapter from an existing file descriptor.
|
||||||
|
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
|
||||||
|
return fmt.Errorf("setup via FD not supported on this platform")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tun *TunAdapter) setupAddress(addr string) error {
|
||||||
|
var sfd int
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ip, prefix, err := net.ParseCIDR(addr)
|
||||||
|
if err != nil {
|
||||||
|
tun.log.Errorf("Error in ParseCIDR: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create system socket
|
||||||
|
if sfd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil {
|
||||||
|
tun.log.Printf("Create AF_INET6 socket failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Friendly output
|
||||||
|
tun.log.Infof("Interface name: %s", tun.Name())
|
||||||
|
tun.log.Infof("Interface IPv6: %s", addr)
|
||||||
|
tun.log.Infof("Interface MTU: %d", tun.mtu)
|
||||||
|
|
||||||
|
// Create the address request
|
||||||
|
var ar in6_aliasreq
|
||||||
|
copy(ar.ifra_name[:], tun.Name())
|
||||||
|
|
||||||
|
ar.ifra_addr.setSockaddr(ip)
|
||||||
|
|
||||||
|
prefixmask := net.CIDRMask(prefix.Mask.Size())
|
||||||
|
ar.ifra_prefixmask.setSockaddr(prefixmask)
|
||||||
|
|
||||||
|
ar.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME
|
||||||
|
ar.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME
|
||||||
|
|
||||||
|
// Set the interface address
|
||||||
|
if err = unix.IoctlSetInt(sfd, SIOCAIFADDR_IN6, int(uintptr(unsafe.Pointer(&ar)))); err != nil {
|
||||||
|
tun.log.Errorf("Error in SIOCAIFADDR_IN6: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
//go:build !linux && !darwin && !windows && !openbsd && !freebsd && !mobile
|
//go:build !linux && !darwin && !ios && !android && !windows && !openbsd && !freebsd && !mobile
|
||||||
// +build !linux,!darwin,!windows,!openbsd,!freebsd,!mobile
|
// +build !linux,!darwin,!ios,!android,!windows,!openbsd,!freebsd,!mobile
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ package tun
|
||||||
// If your platform supports tun devices, you could try configuring it manually
|
// If your platform supports tun devices, you could try configuring it manually
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
wgtun "golang.zx2c4.com/wireguard/tun"
|
wgtun "golang.zx2c4.com/wireguard/tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,7 +16,7 @@ import (
|
||||||
func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
||||||
iface, err := wgtun.CreateTUN(ifname, mtu)
|
iface, err := wgtun.CreateTUN(ifname, mtu)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return fmt.Errorf("failed to create TUN: %w", err)
|
||||||
}
|
}
|
||||||
tun.iface = iface
|
tun.iface = iface
|
||||||
if mtu, err := iface.MTU(); err == nil {
|
if mtu, err := iface.MTU(); err == nil {
|
||||||
|
@ -22,7 +24,15 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
||||||
} else {
|
} else {
|
||||||
tun.mtu = 0
|
tun.mtu = 0
|
||||||
}
|
}
|
||||||
return tun.setupAddress(addr)
|
if addr != "" {
|
||||||
|
return tun.setupAddress(addr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configures the "utun" adapter from an existing file descriptor.
|
||||||
|
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
|
||||||
|
return fmt.Errorf("setup via FD not supported on this platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't know how to set the IPv6 address on an unknown platform, therefore
|
// We don't know how to set the IPv6 address on an unknown platform, therefore
|
||||||
|
|
|
@ -4,14 +4,16 @@
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wintun"
|
||||||
wgtun "golang.zx2c4.com/wireguard/tun"
|
wgtun "golang.zx2c4.com/wireguard/tun"
|
||||||
"golang.zx2c4.com/wireguard/windows/elevate"
|
"golang.zx2c4.com/wireguard/windows/elevate"
|
||||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||||
|
@ -22,7 +24,7 @@ import (
|
||||||
// Configures the TUN adapter with the correct IPv6 address and MTU.
|
// Configures the TUN adapter with the correct IPv6 address and MTU.
|
||||||
func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
||||||
if ifname == "auto" {
|
if ifname == "auto" {
|
||||||
ifname = defaults.GetDefaults().DefaultIfName
|
ifname = config.GetDefaults().DefaultIfName
|
||||||
}
|
}
|
||||||
return elevate.DoAsSystem(func() error {
|
return elevate.DoAsSystem(func() error {
|
||||||
var err error
|
var err error
|
||||||
|
@ -31,13 +33,27 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
||||||
if guid, err = windows.GUIDFromString("{8f59971a-7872-4aa6-b2eb-061fc4e9d0a7}"); err != nil {
|
if guid, err = windows.GUIDFromString("{8f59971a-7872-4aa6-b2eb-061fc4e9d0a7}"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu)); err != nil {
|
iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu))
|
||||||
return err
|
if err != nil {
|
||||||
|
// Very rare condition, it will purge the old device and create new
|
||||||
|
tun.log.Printf("Error creating TUN: '%s'", err)
|
||||||
|
wintun.Uninstall()
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
tun.log.Printf("Trying again")
|
||||||
|
iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
tun.log.Printf("Waiting for TUN to come up")
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
tun.iface = iface
|
tun.iface = iface
|
||||||
if err = tun.setupAddress(addr); err != nil {
|
if addr != "" {
|
||||||
tun.log.Errorln("Failed to set up TUN address:", err)
|
tun.log.Printf("Setting up address")
|
||||||
return err
|
if err = tun.setupAddress(addr); err != nil {
|
||||||
|
tun.log.Errorln("Failed to set up TUN address:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err = tun.setupMTU(getSupportedMTU(mtu)); err != nil {
|
if err = tun.setupMTU(getSupportedMTU(mtu)); err != nil {
|
||||||
tun.log.Errorln("Failed to set up TUN MTU:", err)
|
tun.log.Errorln("Failed to set up TUN MTU:", err)
|
||||||
|
@ -46,10 +62,16 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
|
||||||
if mtu, err := iface.MTU(); err == nil {
|
if mtu, err := iface.MTU(); err == nil {
|
||||||
tun.mtu = uint64(mtu)
|
tun.mtu = uint64(mtu)
|
||||||
}
|
}
|
||||||
|
tun.log.Printf("TUN is set up successfully")
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configures the "utun" adapter from an existing file descriptor.
|
||||||
|
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
|
||||||
|
return fmt.Errorf("setup via FD not supported on this platform")
|
||||||
|
}
|
||||||
|
|
||||||
// Sets the MTU of the TUN adapter.
|
// Sets the MTU of the TUN adapter.
|
||||||
func (tun *TunAdapter) setupMTU(mtu uint64) error {
|
func (tun *TunAdapter) setupMTU(mtu uint64) error {
|
||||||
if tun.iface == nil || tun.Name() == "" {
|
if tun.iface == nil || tun.Name() == "" {
|
||||||
|
@ -83,13 +105,9 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
||||||
return errors.New("Can't configure IPv6 address as TUN adapter is not present")
|
return errors.New("Can't configure IPv6 address as TUN adapter is not present")
|
||||||
}
|
}
|
||||||
if intf, ok := tun.iface.(*wgtun.NativeTun); ok {
|
if intf, ok := tun.iface.(*wgtun.NativeTun); ok {
|
||||||
if ipaddr, ipnet, err := net.ParseCIDR(addr); err == nil {
|
if ipnet, err := netip.ParsePrefix(addr); err == nil {
|
||||||
luid := winipcfg.LUID(intf.LUID())
|
luid := winipcfg.LUID(intf.LUID())
|
||||||
addresses := append([]net.IPNet{}, net.IPNet{
|
addresses := []netip.Prefix{ipnet}
|
||||||
IP: ipaddr,
|
|
||||||
Mask: ipnet.Mask,
|
|
||||||
})
|
|
||||||
|
|
||||||
err := luid.SetIPAddressesForFamily(windows.AF_INET6, addresses)
|
err := luid.SetIPAddressesForFamily(windows.AF_INET6, addresses)
|
||||||
if err == windows.ERROR_OBJECT_ALREADY_EXISTS {
|
if err == windows.ERROR_OBJECT_ALREADY_EXISTS {
|
||||||
cleanupAddressesOnDisconnectedInterfaces(windows.AF_INET6, addresses)
|
cleanupAddressesOnDisconnectedInterfaces(windows.AF_INET6, addresses)
|
||||||
|
@ -112,24 +130,13 @@ func (tun *TunAdapter) setupAddress(addr string) error {
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []net.IPNet) {
|
func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []netip.Prefix) {
|
||||||
if len(addresses) == 0 {
|
if len(addresses) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
includedInAddresses := func(a net.IPNet) bool {
|
addrHash := make(map[netip.Addr]bool, len(addresses))
|
||||||
// TODO: this makes the whole algorithm O(n^2). But we can't stick net.IPNet in a Go hashmap. Bummer!
|
for i := range addresses {
|
||||||
for _, addr := range addresses {
|
addrHash[addresses[i].Addr()] = true
|
||||||
ip := addr.IP
|
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
|
||||||
ip = ip4
|
|
||||||
}
|
|
||||||
mA, _ := addr.Mask.Size()
|
|
||||||
mB, _ := a.Mask.Size()
|
|
||||||
if bytes.Equal(ip, a.IP) && mA == mB {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault)
|
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -140,11 +147,10 @@ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, add
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
|
for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
|
||||||
ip := address.Address.IP()
|
if ip, _ := netip.AddrFromSlice(address.Address.IP()); addrHash[ip] {
|
||||||
ipnet := net.IPNet{IP: ip, Mask: net.CIDRMask(int(address.OnLinkPrefixLength), 8*len(ip))}
|
prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength))
|
||||||
if includedInAddresses(ipnet) {
|
log.Printf("Cleaning up stale address %s from interface ‘%s’", prefix.String(), iface.FriendlyName())
|
||||||
log.Printf("Cleaning up stale address %s from interface ‘%s’", ipnet.String(), iface.FriendlyName())
|
iface.LUID.DeleteIPAddress(prefix)
|
||||||
iface.LUID.DeleteIPAddress(ipnet)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue