mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2025-04-28 06:05:06 +03:00
Compare commits
383 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 | ||
![]() |
14f1cd4696 | ||
![]() |
b0f6544b07 | ||
![]() |
48d278bd2a | ||
![]() |
596f16aa6c | ||
![]() |
ae24f5de38 | ||
![]() |
cba667f28d | ||
![]() |
9df3bc0066 | ||
![]() |
e824c73e21 | ||
![]() |
7efd66932f | ||
![]() |
0da871f528 | ||
![]() |
6fed2a75d7 | ||
![]() |
110613b234 | ||
![]() |
6112c9cf18 | ||
![]() |
590d83aa9c | ||
![]() |
ee33bd248f | ||
![]() |
cfa293d189 | ||
![]() |
98b0aaf747 | ||
![]() |
4c66a13b93 | ||
![]() |
f08dec822a | ||
![]() |
9a9452dcc8 | ||
![]() |
65e350153e | ||
![]() |
35ea66d651 | ||
![]() |
8fe1c41295 | ||
![]() |
d66b3ffb7a | ||
![]() |
63c4cb5c21 | ||
![]() |
0a1a155e66 | ||
![]() |
c55611a478 | ||
![]() |
22caddef63 | ||
![]() |
81839ad50d | ||
![]() |
b8a2d9f125 | ||
![]() |
8ce7c86383 | ||
![]() |
69782ad87b | ||
![]() |
ee21c56e43 | ||
![]() |
69632bacb5 | ||
![]() |
962665189c | ||
![]() |
428d2375da | ||
![]() |
8cf76f841d | ||
![]() |
7db934488e | ||
![]() |
c922eba2d8 | ||
![]() |
1de587a971 | ||
![]() |
d9fe6f72ac | ||
![]() |
d24d3fa047 | ||
![]() |
e165b1fa0c | ||
![]() |
01c44a087b | ||
![]() |
217ac39e77 | ||
![]() |
0abfe78858 | ||
![]() |
5ad8c33d26 | ||
![]() |
b67c313f44 | ||
![]() |
5ef61faeff | ||
![]() |
414aaf6eb9 | ||
![]() |
88a393a7b3 | ||
![]() |
dc9720e580 | ||
![]() |
5477566fa9 | ||
![]() |
9cdfd59476 | ||
![]() |
a7d06e048a | ||
![]() |
b1f61fb0a8 | ||
![]() |
493208fb37 | ||
![]() |
dad0b10dfe | ||
![]() |
c6fe81b5d2 | ||
![]() |
4f2abece81 | ||
![]() |
486ffebedd | ||
![]() |
af99fa4f6b | ||
![]() |
a182fad8d6 | ||
![]() |
f8e626dbe1 | ||
![]() |
dd66e8a9c9 | ||
![]() |
16b8149052 | ||
![]() |
d5c0dc9bee | ||
![]() |
4c889703b1 | ||
![]() |
5616b9fc84 | ||
![]() |
41b4bf69cf | ||
![]() |
36c754cd0d | ||
![]() |
8c454a146c | ||
![]() |
df7ca3a5b8 | ||
![]() |
234addc81f | ||
![]() |
96ba6f0fd9 | ||
![]() |
e4ec277683 | ||
![]() |
88a0a3e8fb | ||
![]() |
c19319df5e | ||
![]() |
4ddebb338d | ||
![]() |
e13657d2ca | ||
![]() |
42d4298e19 | ||
![]() |
5e89ab706f | ||
![]() |
b77b018c4d | ||
![]() |
c3de1542b0 | ||
![]() |
55f7874b35 | ||
![]() |
e9caf989b8 | ||
![]() |
d2308f8d3a | ||
![]() |
bc78530fcb | ||
![]() |
073799d3de | ||
![]() |
41d890bb64 | ||
![]() |
90f9be38c5 | ||
![]() |
c7ffbc05a5 | ||
![]() |
93c94e38f9 | ||
![]() |
6c4778bb67 | ||
![]() |
0c4c385885 | ||
![]() |
559e31c502 | ||
![]() |
31717a8578 | ||
![]() |
315e222173 | ||
![]() |
2d2ad4692b | ||
![]() |
9f5cc0eecb | ||
![]() |
620b901473 | ||
![]() |
09ea351682 | ||
![]() |
6d92edd405 | ||
![]() |
a4bdf3de32 | ||
![]() |
408d381591 | ||
![]() |
87e936195e | ||
![]() |
e4e58831bf | ||
![]() |
03a5cce5bb | ||
![]() |
1f64319712 | ||
![]() |
4f3117d81d | ||
![]() |
5c19f3f88c | ||
![]() |
feb02c485a | ||
![]() |
4859accbb0 | ||
![]() |
99227b60ce | ||
![]() |
f92d812f3c | ||
![]() |
6af9b61b15 | ||
![]() |
f2d1eff8f6 | ||
![]() |
9a1d1df85e | ||
![]() |
e5d638ff4b | ||
![]() |
86e5306eec | ||
![]() |
529a33034b | ||
![]() |
1c7deb72db | ||
![]() |
52345a2de4 | ||
![]() |
571186ca77 | ||
![]() |
3c89781057 | ||
![]() |
a5f2ba80a2 | ||
![]() |
538ee13669 | ||
![]() |
3613614b41 | ||
![]() |
ebe366ef3b | ||
![]() |
cbb6dc1b7d | ||
![]() |
d1cd671bec | ||
![]() |
bbdff033ce | ||
![]() |
f094cf34bf | ||
![]() |
d8df9755f2 | ||
![]() |
b333c7d7f3 | ||
![]() |
6a0ddc20ef | ||
![]() |
52309d094c | ||
![]() |
747a2538d7 | ||
![]() |
04ecdf6045 | ||
![]() |
cd5383f7b7 | ||
![]() |
3704ebf4cb | ||
![]() |
e224c02d6d | ||
![]() |
e4ce2c79a9 | ||
![]() |
f990a56046 |
110 changed files with 6959 additions and 3658 deletions
|
@ -1,230 +0,0 @@
|
|||
# Golang CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
||||
version: 2.1
|
||||
jobs:
|
||||
lint:
|
||||
docker:
|
||||
- image: circleci/golang:1.16
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Run golangci-lint
|
||||
command: |
|
||||
go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.31.0
|
||||
golangci-lint run
|
||||
|
||||
- run:
|
||||
name: Run Go tests
|
||||
command: |
|
||||
go test ./...
|
||||
|
||||
build-linux:
|
||||
docker:
|
||||
- image: circleci/golang:1.16
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Create artifact upload directory and set variables
|
||||
command: |
|
||||
mkdir /tmp/upload
|
||||
echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV
|
||||
echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV
|
||||
echo 'export CIVERSIONRPM=$(sh contrib/semver/version.sh --bare | tr "-" ".")' >> $BASH_ENV
|
||||
echo 'export CIBRANCH=$(echo $CIRCLE_BRANCH | tr -d "/")' >> $BASH_ENV
|
||||
case "$CINAME" in \
|
||||
"yggdrasil") (echo 'export CICONFLICTS=yggdrasil-develop' >> $BASH_ENV) ;; \
|
||||
"yggdrasil-develop") (echo 'export CICONFLICTS=yggdrasil' >> $BASH_ENV) ;; \
|
||||
*) (echo 'export CICONFLICTS="yggdrasil yggdrasil-develop"' >> $BASH_ENV) ;; \
|
||||
esac
|
||||
git config --global user.email "$(git log --format='%ae' HEAD -1)";
|
||||
git config --global user.name "$(git log --format='%an' HEAD -1)";
|
||||
|
||||
- run:
|
||||
name: Install RPM utilities
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y rpm file
|
||||
mkdir -p ~/rpmbuild/BUILD ~/rpmbuild/RPMS ~/rpmbuild/SOURCES ~/rpmbuild/SPECS ~/rpmbuild/SRPMS
|
||||
|
||||
- run:
|
||||
name: Test debug builds
|
||||
command: |
|
||||
./build -d
|
||||
test -f yggdrasil && test -f yggdrasilctl
|
||||
|
||||
- run:
|
||||
name: Build for Linux (including Debian packages)
|
||||
command: |
|
||||
rm -f {yggdrasil,yggdrasilctl}
|
||||
PKGARCH=amd64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-amd64;
|
||||
PKGARCH=i386 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-i386;
|
||||
PKGARCH=mipsel sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mipsel && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mipsel;
|
||||
PKGARCH=mips sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mips && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mips;
|
||||
PKGARCH=armhf sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armhf && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armhf;
|
||||
PKGARCH=armel sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armel && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armel;
|
||||
PKGARCH=arm64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-arm64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-arm64;
|
||||
mv *.deb /tmp/upload/
|
||||
|
||||
- run:
|
||||
name: Build for Linux (RPM packages)
|
||||
command: |
|
||||
git clone https://github.com/yggdrasil-network/yggdrasil-package-rpm ~/rpmbuild/SPECS
|
||||
cd ../ && tar -czvf ~/rpmbuild/SOURCES/v$CIVERSIONRPM --transform "s/project/yggdrasil-go-$CIBRANCH-$CIVERSIONRPM/" project
|
||||
sed -i "s/yggdrasil-go/yggdrasil-go-$CIBRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec
|
||||
sed -i "s/^PKGNAME=yggdrasil/PKGNAME=yggdrasil-$CIBRANCH/" ~/rpmbuild/SPECS/yggdrasil.spec
|
||||
sed -i "s/^Name\:.*/Name\: $CINAME/" ~/rpmbuild/SPECS/yggdrasil.spec
|
||||
sed -i "s/^Version\:.*/Version\: $CIVERSIONRPM/" ~/rpmbuild/SPECS/yggdrasil.spec
|
||||
sed -i "s/^Conflicts\:.*/Conflicts\: $CICONFLICTS/" ~/rpmbuild/SPECS/yggdrasil.spec
|
||||
cat ~/rpmbuild/SPECS/yggdrasil.spec
|
||||
GOARCH=amd64 rpmbuild -v --nodeps --target=x86_64 -ba ~/rpmbuild/SPECS/yggdrasil.spec
|
||||
#GOARCH=386 rpmbuild -v --nodeps --target=i386 -bb ~/rpmbuild/SPECS/yggdrasil.spec
|
||||
find ~/rpmbuild/RPMS/ -name '*.rpm' -exec mv {} /tmp/upload \;
|
||||
find ~/rpmbuild/SRPMS/ -name '*.rpm' -exec mv {} /tmp/upload \;
|
||||
|
||||
- run:
|
||||
name: Build for EdgeRouter and VyOS
|
||||
command: |
|
||||
rm -f {yggdrasil,yggdrasilctl}
|
||||
git clone https://github.com/neilalexander/vyatta-yggdrasil /tmp/vyatta-yggdrasil;
|
||||
cd /tmp/vyatta-yggdrasil;
|
||||
BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-edgerouter-x $CIRCLE_BRANCH;
|
||||
BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-edgerouter-lite $CIRCLE_BRANCH;
|
||||
BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-vyos-i386 $CIRCLE_BRANCH
|
||||
BUILDDIR_YGG=$CIRCLE_WORKING_DIRECTORY ./build-vyos-amd64 $CIRCLE_BRANCH
|
||||
mv *.deb /tmp/upload;
|
||||
|
||||
- persist_to_workspace:
|
||||
root: /tmp
|
||||
paths:
|
||||
- upload
|
||||
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: "10.0.0"
|
||||
|
||||
working_directory: ~/go/src/github.com/yggdrasil-network/yggdrasil-go
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Create artifact upload directory and set variables
|
||||
command: |
|
||||
mkdir /tmp/upload
|
||||
echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV
|
||||
echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV
|
||||
echo 'export PATH=$PATH:/usr/local/go/bin:~/go/bin' >> $BASH_ENV
|
||||
git config --global user.email "$(git log --format='%ae' HEAD -1)";
|
||||
git config --global user.name "$(git log --format='%an' HEAD -1)";
|
||||
echo -e "Host *\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
|
||||
|
||||
- run:
|
||||
name: Install Go 1.16
|
||||
command: |
|
||||
cd /tmp
|
||||
curl -LO https://dl.google.com/go/go1.16.darwin-amd64.pkg
|
||||
sudo installer -pkg /tmp/go1.16.darwin-amd64.pkg -target /
|
||||
|
||||
#- run:
|
||||
# name: Install Gomobile
|
||||
# command: |
|
||||
# GO111MODULE=off go get golang.org/x/mobile/cmd/gomobile
|
||||
# gomobile init
|
||||
|
||||
- run:
|
||||
name: Build for macOS
|
||||
command: |
|
||||
GO111MODULE=on GOOS=darwin GOARCH=amd64 ./build
|
||||
cp yggdrasil /tmp/upload/$CINAME-$CIVERSION-darwin-amd64
|
||||
cp yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-darwin-amd64;
|
||||
|
||||
- run:
|
||||
name: Build for macOS (.pkg format)
|
||||
command: |
|
||||
PKGARCH=amd64 sh contrib/macos/create-pkg.sh
|
||||
mv *.pkg /tmp/upload/
|
||||
|
||||
#- run:
|
||||
# name: Build framework for iOS (.framework format)
|
||||
# command: |
|
||||
# sudo GO111MODULE=off go get -v github.com/yggdrasil-network/yggdrasil-go/cmd/...
|
||||
# sudo GO111MODULE=off go get -v github.com/yggdrasil-network/yggdrasil-go/src/...
|
||||
# GO111MODULE=off ./build -i
|
||||
# mv *.framework /tmp/upload
|
||||
|
||||
- persist_to_workspace:
|
||||
root: /tmp
|
||||
paths:
|
||||
- upload
|
||||
|
||||
build-other:
|
||||
docker:
|
||||
- image: circleci/golang:1.16
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Create artifact upload directory and set variables
|
||||
command: |
|
||||
mkdir /tmp/upload
|
||||
echo 'export CINAME=$(sh contrib/semver/name.sh)' >> $BASH_ENV
|
||||
echo 'export CIVERSION=$(sh contrib/semver/version.sh --bare)' >> $BASH_ENV
|
||||
git config --global user.email "$(git log --format='%ae' HEAD -1)";
|
||||
git config --global user.name "$(git log --format='%an' HEAD -1)";
|
||||
|
||||
- run:
|
||||
name: Build for OpenBSD
|
||||
command: |
|
||||
rm -f {yggdrasil,yggdrasilctl}
|
||||
GOOS=openbsd GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-openbsd-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-openbsd-amd64;
|
||||
GOOS=openbsd GOARCH=386 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-openbsd-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-openbsd-i386;
|
||||
|
||||
- run:
|
||||
name: Build for FreeBSD
|
||||
command: |
|
||||
rm -f {yggdrasil,yggdrasilctl}
|
||||
GOOS=freebsd GOARCH=amd64 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-freebsd-amd64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-freebsd-amd64;
|
||||
GOOS=freebsd GOARCH=386 ./build && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-freebsd-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-freebsd-i386;
|
||||
|
||||
- run:
|
||||
name: Build for Windows
|
||||
command: |
|
||||
rm -f {yggdrasil,yggdrasilctl}
|
||||
GOOS=windows GOARCH=amd64 ./build && mv yggdrasil.exe /tmp/upload/$CINAME-$CIVERSION-windows-amd64.exe && mv yggdrasilctl.exe /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-windows-amd64.exe;
|
||||
GOOS=windows GOARCH=386 ./build && mv yggdrasil.exe /tmp/upload/$CINAME-$CIVERSION-windows-i386.exe && mv yggdrasilctl.exe /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-windows-i386.exe;
|
||||
|
||||
- persist_to_workspace:
|
||||
root: /tmp
|
||||
paths:
|
||||
- upload
|
||||
|
||||
upload:
|
||||
machine: true
|
||||
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: /tmp
|
||||
|
||||
- store_artifacts:
|
||||
path: /tmp/upload
|
||||
destination: /
|
||||
|
||||
workflows:
|
||||
version: 2.1
|
||||
build:
|
||||
jobs:
|
||||
- lint
|
||||
- build-linux
|
||||
- build-macos
|
||||
- build-other
|
||||
- upload:
|
||||
requires:
|
||||
- build-linux
|
||||
- build-macos
|
||||
- build-other
|
157
.github/workflows/ci.yml
vendored
Normal file
157
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,157 @@
|
|||
name: Yggdrasil
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
release:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
- uses: actions/checkout@v4
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
args: --issues-exit-code=1
|
||||
|
||||
codeql:
|
||||
name: Analyse
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: go
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
build-linux:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goversion: ["1.22", "1.23", "1.24"]
|
||||
|
||||
name: Build & Test (Linux, 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 ./...
|
||||
|
||||
- name: Unit tests
|
||||
run: go test -v ./...
|
||||
|
||||
build-windows:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goversion: ["1.22", "1.23", "1.24"]
|
||||
|
||||
name: Build & Test (Windows, Go ${{ matrix.goversion }})
|
||||
needs: [lint]
|
||||
|
||||
runs-on: windows-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 ./...
|
||||
|
||||
- name: Unit tests
|
||||
run: go test -v ./...
|
||||
|
||||
build-macos:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goversion: ["1.22", "1.23", "1.24"]
|
||||
|
||||
name: Build & Test (macOS, Go ${{ matrix.goversion }})
|
||||
needs: [lint]
|
||||
|
||||
runs-on: macos-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 ./...
|
||||
|
||||
- name: Unit tests
|
||||
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:
|
||||
name: All tests passed
|
||||
needs: [lint, codeql, build-linux, build-windows, build-macos]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !cancelled() }}
|
||||
steps:
|
||||
- name: Check all tests passed
|
||||
uses: re-actors/alls-green@release/v1
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
140
.github/workflows/pkg.yml
vendored
Normal file
140
.github/workflows/pkg.yml
vendored
Normal file
|
@ -0,0 +1,140 @@
|
|||
name: Packages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-packages-debian:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pkgarch: ["amd64", "i386", "mips", "mipsel", "armhf", "armel", "arm64"]
|
||||
|
||||
name: Package (Debian, ${{ matrix.pkgarch }})
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "stable"
|
||||
|
||||
- name: Build package
|
||||
env:
|
||||
PKGARCH: ${{ matrix.pkgarch }}
|
||||
run: sh contrib/deb/generate.sh
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Debian package (${{ matrix.pkgarch }})
|
||||
path: "*.deb"
|
||||
if-no-files-found: error
|
||||
|
||||
build-packages-macos:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pkgarch: ["amd64", "arm64"]
|
||||
|
||||
name: Package (macOS, ${{ matrix.pkgarch }})
|
||||
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "stable"
|
||||
|
||||
- name: Build package
|
||||
env:
|
||||
PKGARCH: ${{ matrix.pkgarch }}
|
||||
run: sh contrib/macos/create-pkg.sh
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macOS package (${{ matrix.pkgarch }})
|
||||
path: "*.pkg"
|
||||
if-no-files-found: error
|
||||
|
||||
build-packages-windows:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pkgarch: ["x64", "x86", "arm", "arm64"]
|
||||
|
||||
name: Package (Windows, ${{ matrix.pkgarch }})
|
||||
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "stable"
|
||||
|
||||
- name: Setup .NET Core SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
|
||||
- name: Build package
|
||||
run: sh contrib/msi/build-msi.sh ${{ matrix.pkgarch }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Windows package (${{ matrix.pkgarch }})
|
||||
path: "*.msi"
|
||||
if-no-files-found: error
|
||||
|
||||
build-packages-router:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pkgarch: ["edgerouter-x", "edgerouter-lite", "vyos-amd64", "vyos-i386"]
|
||||
|
||||
name: Package (Router, ${{ matrix.pkgarch }})
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
path: yggdrasil
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: neilalexander/vyatta-yggdrasil
|
||||
path: vyatta-yggdrasil
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "stable"
|
||||
|
||||
- name: Build package
|
||||
env:
|
||||
BUILDDIR_YGG: /home/runner/work/yggdrasil-go/yggdrasil-go/yggdrasil
|
||||
run: cd /home/runner/work/yggdrasil-go/yggdrasil-go/vyatta-yggdrasil && ./build-${{ matrix.pkgarch }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Router package (${{ matrix.pkgarch }})
|
||||
path: "/home/runner/work/yggdrasil-go/yggdrasil-go/vyatta-yggdrasil/*.deb"
|
||||
if-no-files-found: error
|
|
@ -2,9 +2,10 @@ run:
|
|||
build-tags:
|
||||
- lint
|
||||
issues-exit-code: 0 # TODO: change this to 1 when we want it to fail builds
|
||||
skip-dirs:
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- contrib/
|
||||
- misc/
|
||||
linters:
|
||||
disable:
|
||||
- gocyclo
|
||||
- gocyclo
|
||||
|
|
1072
CHANGELOG.md
1072
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,6 @@
|
|||
# Yggdrasil
|
||||
|
||||
[](https://circleci.com/gh/yggdrasil-network/yggdrasil-go)
|
||||
[](https://github.com/yggdrasil-network/yggdrasil-go/actions/workflows/ci.yml)
|
||||
|
||||
## Introduction
|
||||
|
||||
|
@ -16,7 +15,7 @@ connectivity - it also works over IPv4.
|
|||
Yggdrasil works on a number of platforms, including Linux, macOS, Ubiquiti
|
||||
EdgeRouter, VyOS, Windows, FreeBSD, OpenBSD and OpenWrt.
|
||||
|
||||
Please see our [Installation](https://yggdrasil-network.github.io/installation.html)
|
||||
Please see our [Installation](https://yggdrasil-network.github.io/installation.html)
|
||||
page for more information. You may also find other platform-specific wrappers, scripts
|
||||
or tools in the `contrib` folder.
|
||||
|
||||
|
@ -25,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
|
||||
packages:
|
||||
|
||||
1. Install [Go](https://golang.org) (requires Go 1.16 or later)
|
||||
1. Install [Go](https://golang.org) (requires Go 1.22 or later)
|
||||
2. Clone this repository
|
||||
2. Run `./build`
|
||||
|
||||
|
@ -57,6 +56,7 @@ other configuration such as listen addresses or multicast addresses, etc.
|
|||
### Run Yggdrasil
|
||||
|
||||
To run with the generated static configuration:
|
||||
|
||||
```
|
||||
./yggdrasil -useconffile /path/to/yggdrasil.conf
|
||||
```
|
||||
|
|
20
appveyor.yml
20
appveyor.yml
|
@ -1,20 +0,0 @@
|
|||
version: '{build}'
|
||||
pull_requests:
|
||||
do_not_increment_build_number: true
|
||||
os: Visual Studio 2019
|
||||
shallow_clone: false
|
||||
|
||||
environment:
|
||||
MSYS2_PATH_TYPE: inherit
|
||||
CHERE_INVOKING: enabled_from_arguments
|
||||
|
||||
build_script:
|
||||
- cmd: >-
|
||||
cd %APPVEYOR_BUILD_FOLDER%
|
||||
- c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x64"
|
||||
- c:\msys64\usr\bin\bash -lc "./contrib/msi/build-msi.sh x86"
|
||||
|
||||
test: off
|
||||
|
||||
artifacts:
|
||||
- path: '*.msi'
|
32
build
32
build
|
@ -9,13 +9,11 @@ PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)}
|
|||
LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER"
|
||||
ARGS="-v"
|
||||
|
||||
while getopts "uaitc:l:dro:p" option
|
||||
while getopts "utc:l:dro:p" option
|
||||
do
|
||||
case "$option"
|
||||
in
|
||||
u) UPX=true;;
|
||||
i) IOS=true;;
|
||||
a) ANDROID=true;;
|
||||
t) TABLES=true;;
|
||||
c) GCFLAGS="$GCFLAGS $OPTARG";;
|
||||
l) LDFLAGS="$LDFLAGS $OPTARG";;
|
||||
|
@ -30,25 +28,11 @@ if [ -z $TABLES ] && [ -z $DEBUG ]; then
|
|||
LDFLAGS="$LDFLAGS -s -w"
|
||||
fi
|
||||
|
||||
if [ $IOS ]; then
|
||||
echo "Building framework for iOS"
|
||||
go get golang.org/x/mobile/bind
|
||||
gomobile bind -target ios -tags mobile -o Yggdrasil.framework -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \
|
||||
github.com/yggdrasil-network/yggdrasil-extras/src/mobile \
|
||||
github.com/yggdrasil-network/yggdrasil-go/src/config
|
||||
elif [ $ANDROID ]; then
|
||||
echo "Building aar for Android"
|
||||
go get golang.org/x/mobile/bind
|
||||
gomobile bind -target android -tags mobile -o yggdrasil.aar -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \
|
||||
github.com/yggdrasil-network/yggdrasil-extras/src/mobile \
|
||||
github.com/yggdrasil-network/yggdrasil-go/src/config
|
||||
else
|
||||
for CMD in yggdrasil yggdrasilctl ; do
|
||||
echo "Building: $CMD"
|
||||
go build $ARGS -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" ./cmd/$CMD
|
||||
for CMD in yggdrasil yggdrasilctl ; do
|
||||
echo "Building: $CMD"
|
||||
go build $ARGS -ldflags="$LDFLAGS" -gcflags="$GCFLAGS" ./cmd/$CMD
|
||||
|
||||
if [ $UPX ]; then
|
||||
upx --brute $CMD
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [ $UPX ]; then
|
||||
upx --brute $CMD
|
||||
fi
|
||||
done
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/*
|
||||
|
||||
This file generates crypto keys.
|
||||
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).
|
||||
|
@ -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.
|
||||
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.
|
||||
|
||||
*/
|
||||
package main
|
||||
|
||||
|
@ -18,6 +16,9 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"suah.dev/protect"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
)
|
||||
|
@ -25,10 +26,19 @@ import (
|
|||
type keySet struct {
|
||||
priv ed25519.PrivateKey
|
||||
pub ed25519.PublicKey
|
||||
count uint64
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := protect.Pledge("stdio"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
threads := runtime.GOMAXPROCS(0)
|
||||
fmt.Println("Threads:", threads)
|
||||
start := time.Now()
|
||||
var totalKeys uint64
|
||||
totalKeys = 0
|
||||
var currentBest ed25519.PublicKey
|
||||
newKeys := make(chan keySet, threads)
|
||||
for i := 0; i < threads; i++ {
|
||||
|
@ -37,8 +47,9 @@ func main() {
|
|||
for {
|
||||
newKey := <-newKeys
|
||||
if isBetter(currentBest, newKey.pub) || len(currentBest) == 0 {
|
||||
totalKeys += newKey.count
|
||||
currentBest = newKey.pub
|
||||
fmt.Println("-----")
|
||||
fmt.Println("-----", time.Since(start), "---", totalKeys, "keys tried")
|
||||
fmt.Println("Priv:", hex.EncodeToString(newKey.priv))
|
||||
fmt.Println("Pub:", hex.EncodeToString(newKey.pub))
|
||||
addr := address.AddrForKey(newKey.pub)
|
||||
|
@ -61,11 +72,14 @@ func isBetter(oldPub, newPub ed25519.PublicKey) bool {
|
|||
|
||||
func doKeys(out chan<- keySet) {
|
||||
bestKey := make(ed25519.PublicKey, ed25519.PublicKeySize)
|
||||
var count uint64
|
||||
count = 0
|
||||
for idx := range bestKey {
|
||||
bestKey[idx] = 0xff
|
||||
}
|
||||
for {
|
||||
pub, priv, err := ed25519.GenerateKey(nil)
|
||||
count++
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -73,6 +87,7 @@ func doKeys(out chan<- keySet) {
|
|||
continue
|
||||
}
|
||||
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,158 +1,340 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"suah.dev/protect"
|
||||
|
||||
"github.com/gologme/log"
|
||||
gsyslog "github.com/hashicorp/go-syslog"
|
||||
"github.com/hjson/hjson-go"
|
||||
"github.com/hjson/hjson-go/v4"
|
||||
"github.com/kardianos/minwinsvc"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||
"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/core"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/tuntap"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/tun"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/version"
|
||||
)
|
||||
|
||||
type node struct {
|
||||
core core.Core
|
||||
config *config.NodeConfig
|
||||
tuntap *tuntap.TunAdapter
|
||||
core *core.Core
|
||||
tun *tun.TunAdapter
|
||||
multicast *multicast.Multicast
|
||||
admin *admin.AdminSocket
|
||||
}
|
||||
|
||||
func readConfig(log *log.Logger, useconf bool, useconffile string, normaliseconf bool) *config.NodeConfig {
|
||||
// Use a configuration file. If -useconf, the configuration will be read
|
||||
// from stdin. If -useconffile, the configuration will be read from the
|
||||
// filesystem.
|
||||
var conf []byte
|
||||
var err error
|
||||
if useconffile != "" {
|
||||
// Read the file from the filesystem
|
||||
conf, err = ioutil.ReadFile(useconffile)
|
||||
// The main function is responsible for configuring and starting Yggdrasil.
|
||||
func main() {
|
||||
// Not all operations are coverable with pledge(2), so immediately
|
||||
// limit file system access with unveil(2), effectively preventing
|
||||
// "proc exec" promises right from the start:
|
||||
//
|
||||
// - read arbitrary config file
|
||||
// - create/write arbitrary log file
|
||||
// - 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 {
|
||||
// Read the file from stdin.
|
||||
conf, err = ioutil.ReadAll(os.Stdin)
|
||||
setLogLevel(*loglevel, logger)
|
||||
}
|
||||
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
|
||||
// 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)
|
||||
|
||||
privateKey := ed25519.PrivateKey(cfg.PrivateKey)
|
||||
publicKey := privateKey.Public().(ed25519.PublicKey)
|
||||
|
||||
switch {
|
||||
case *getaddr:
|
||||
addr := address.AddrForKey(publicKey)
|
||||
ip := net.IP(addr[:])
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
// Check if we have old field names
|
||||
if _, ok := dat["TunnelRouting"]; ok {
|
||||
log.Warnln("WARNING: Tunnel routing is no longer supported")
|
||||
}
|
||||
if old, ok := dat["SigningPrivateKey"]; ok {
|
||||
log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option has been renamed to \"PrivateKey\"")
|
||||
if _, ok := dat["PrivateKey"]; !ok {
|
||||
if privstr, err := hex.DecodeString(old.(string)); err == nil {
|
||||
priv := ed25519.PrivateKey(privstr)
|
||||
pub := priv.Public().(ed25519.PublicKey)
|
||||
dat["PrivateKey"] = hex.EncodeToString(priv[:])
|
||||
dat["PublicKey"] = hex.EncodeToString(pub[:])
|
||||
} else {
|
||||
log.Warnln("WARNING: The \"SigningPrivateKey\" configuration option contains an invalid value and will be ignored")
|
||||
}
|
||||
}
|
||||
}
|
||||
if oldmc, ok := dat["MulticastInterfaces"]; ok {
|
||||
if oldmcvals, ok := oldmc.([]interface{}); ok {
|
||||
var newmc []config.MulticastInterfaceConfig
|
||||
for _, oldmcval := range oldmcvals {
|
||||
if str, ok := oldmcval.(string); ok {
|
||||
newmc = append(newmc, config.MulticastInterfaceConfig{
|
||||
Regex: str,
|
||||
Beacon: true,
|
||||
Listen: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
if newmc != nil {
|
||||
if oldport, ok := dat["LinkLocalTCPPort"]; ok {
|
||||
// numbers parse to float64 by default
|
||||
if port, ok := oldport.(float64); ok {
|
||||
for idx := range newmc {
|
||||
newmc[idx].Port = uint16(port)
|
||||
}
|
||||
}
|
||||
}
|
||||
dat["MulticastInterfaces"] = newmc
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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
|
||||
// with -genconf.
|
||||
func doGenconf(isjson bool) string {
|
||||
cfg := defaults.GenerateConfig()
|
||||
var bs []byte
|
||||
var err error
|
||||
if isjson {
|
||||
bs, err = json.MarshalIndent(cfg, "", " ")
|
||||
} else {
|
||||
bs, err = hjson.Marshal(cfg)
|
||||
// Promise final modes of operation. At this point, if at all:
|
||||
// - raw socket is created/open
|
||||
// - admin socket is created/open
|
||||
// - privileges are dropped to non-root user
|
||||
//
|
||||
// Peers, InterfacePeers, Listen can be UNIX sockets;
|
||||
// Go's net.Listen.Close() deletes files on shutdown.
|
||||
promises := []string{"stdio", "cpath", "inet", "unix", "dns"}
|
||||
if len(cfg.MulticastInterfaces) > 0 {
|
||||
promises = append(promises, "mcast")
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if err := protect.Pledge(strings.Join(promises, " ")); err != nil {
|
||||
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) {
|
||||
|
@ -180,227 +362,3 @@ func setLogLevel(loglevel string, logger *log.Logger) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
type yggArgs struct {
|
||||
genconf bool
|
||||
useconf bool
|
||||
useconffile string
|
||||
normaliseconf bool
|
||||
confjson bool
|
||||
autoconf bool
|
||||
ver bool
|
||||
logto string
|
||||
getaddr bool
|
||||
getsnet bool
|
||||
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, done chan struct{}) {
|
||||
defer close(done)
|
||||
// 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/TAP 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.
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
// 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
|
||||
default:
|
||||
}
|
||||
|
||||
// Setup the Yggdrasil node itself. The node{} type includes a Core, so we
|
||||
// don't need to create this manually.
|
||||
n := node{config: cfg}
|
||||
// Now start Yggdrasil - this starts the DHT, router, switch and other core
|
||||
// components needed for Yggdrasil to operate
|
||||
if err = n.core.Start(cfg, logger); err != nil {
|
||||
logger.Errorln("An error occurred during startup")
|
||||
panic(err)
|
||||
}
|
||||
// Register the session firewall gatekeeper function
|
||||
// Allocate our modules
|
||||
n.admin = &admin.AdminSocket{}
|
||||
n.multicast = &multicast.Multicast{}
|
||||
n.tuntap = &tuntap.TunAdapter{}
|
||||
// Start the admin socket
|
||||
if err := n.admin.Init(&n.core, cfg, logger, nil); err != nil {
|
||||
logger.Errorln("An error occurred initialising admin socket:", err)
|
||||
} else if err := n.admin.Start(); err != nil {
|
||||
logger.Errorln("An error occurred starting admin socket:", err)
|
||||
}
|
||||
n.admin.SetupAdminHandlers(n.admin)
|
||||
// Start the multicast interface
|
||||
if err := n.multicast.Init(&n.core, cfg, logger, nil); err != nil {
|
||||
logger.Errorln("An error occurred initialising multicast:", err)
|
||||
} else if err := n.multicast.Start(); err != nil {
|
||||
logger.Errorln("An error occurred starting multicast:", err)
|
||||
}
|
||||
n.multicast.SetupAdminHandlers(n.admin)
|
||||
// Start the TUN/TAP interface
|
||||
if err := n.tuntap.Init(&n.core, cfg, logger, nil); err != nil {
|
||||
logger.Errorln("An error occurred initialising TUN/TAP:", err)
|
||||
} else if err := n.tuntap.Start(); err != nil {
|
||||
logger.Errorln("An error occurred starting TUN/TAP:", err)
|
||||
}
|
||||
n.tuntap.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())
|
||||
// Catch interrupts from the operating system to exit gracefully.
|
||||
<-ctx.Done()
|
||||
// Capture the service being stopped on Windows.
|
||||
minwinsvc.SetOnExit(n.shutdown)
|
||||
n.shutdown()
|
||||
}
|
||||
|
||||
func (n *node) shutdown() {
|
||||
_ = n.admin.Stop()
|
||||
_ = n.multicast.Stop()
|
||||
_ = n.tuntap.Stop()
|
||||
n.core.Stop()
|
||||
}
|
||||
|
||||
func main() {
|
||||
args := getArgs()
|
||||
hup := make(chan os.Signal, 1)
|
||||
//signal.Notify(hup, os.Interrupt, syscall.SIGHUP)
|
||||
term := make(chan os.Signal, 1)
|
||||
signal.Notify(term, os.Interrupt, syscall.SIGTERM)
|
||||
for {
|
||||
done := make(chan struct{})
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go run(args, ctx, done)
|
||||
select {
|
||||
case <-hup:
|
||||
cancel()
|
||||
<-done
|
||||
case <-term:
|
||||
cancel()
|
||||
<-done
|
||||
return
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
89
cmd/yggdrasilctl/cmd_line_env.go
Normal file
89
cmd/yggdrasilctl/cmd_line_env.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/hjson/hjson-go/v4"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
)
|
||||
|
||||
type CmdLineEnv struct {
|
||||
args []string
|
||||
endpoint, server string
|
||||
injson, ver bool
|
||||
}
|
||||
|
||||
func newCmdLineEnv() CmdLineEnv {
|
||||
var cmdLineEnv CmdLineEnv
|
||||
cmdLineEnv.endpoint = config.GetDefaults().DefaultAdminListen
|
||||
return cmdLineEnv
|
||||
}
|
||||
|
||||
func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n\n", os.Args[0])
|
||||
fmt.Println("Options:")
|
||||
flag.PrintDefaults()
|
||||
fmt.Println()
|
||||
fmt.Println("Please note that options must always specified BEFORE the command\non the command line or they will be ignored.")
|
||||
fmt.Println()
|
||||
fmt.Println("Commands:\n - Use \"list\" for a list of available commands")
|
||||
fmt.Println()
|
||||
fmt.Println("Examples:")
|
||||
fmt.Println(" - ", os.Args[0], "list")
|
||||
fmt.Println(" - ", os.Args[0], "getPeers")
|
||||
fmt.Println(" - ", os.Args[0], "-endpoint=tcp://localhost:9001 getPeers")
|
||||
fmt.Println(" - ", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getPeers")
|
||||
}
|
||||
|
||||
server := flag.String("endpoint", cmdLineEnv.endpoint, "Admin socket endpoint")
|
||||
injson := flag.Bool("json", false, "Output in JSON format (as opposed to pretty-print)")
|
||||
ver := flag.Bool("version", false, "Prints the version of this build")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
cmdLineEnv.args = flag.Args()
|
||||
cmdLineEnv.server = *server
|
||||
cmdLineEnv.injson = *injson
|
||||
cmdLineEnv.ver = *ver
|
||||
}
|
||||
|
||||
func (cmdLineEnv *CmdLineEnv) setEndpoint(logger *log.Logger) {
|
||||
if cmdLineEnv.server == cmdLineEnv.endpoint {
|
||||
if cfg, err := os.ReadFile(config.GetDefaults().DefaultConfigFile); err == nil {
|
||||
if bytes.Equal(cfg[0:2], []byte{0xFF, 0xFE}) ||
|
||||
bytes.Equal(cfg[0:2], []byte{0xFE, 0xFF}) {
|
||||
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
|
||||
decoder := utf.NewDecoder()
|
||||
cfg, err = decoder.Bytes(cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
var dat map[string]interface{}
|
||||
if err := hjson.Unmarshal(cfg, &dat); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if ep, ok := dat["AdminListen"].(string); ok && (ep != "none" && ep != "") {
|
||||
cmdLineEnv.endpoint = ep
|
||||
logger.Println("Found platform default config file", config.GetDefaults().DefaultConfigFile)
|
||||
logger.Println("Using endpoint", cmdLineEnv.endpoint, "from AdminListen")
|
||||
} else {
|
||||
logger.Println("Configuration file doesn't contain appropriate AdminListen option")
|
||||
logger.Println("Falling back to platform default", config.GetDefaults().DefaultAdminListen)
|
||||
}
|
||||
} else {
|
||||
logger.Println("Can't open config file from default location", config.GetDefaults().DefaultConfigFile)
|
||||
logger.Println("Falling back to platform default", config.GetDefaults().DefaultAdminListen)
|
||||
}
|
||||
} else {
|
||||
cmdLineEnv.endpoint = cmdLineEnv.server
|
||||
logger.Println("Using endpoint", cmdLineEnv.endpoint, "from command line")
|
||||
}
|
||||
}
|
|
@ -6,25 +6,29 @@ import (
|
|||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"suah.dev/protect"
|
||||
|
||||
"github.com/hjson/hjson-go"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/tun"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/version"
|
||||
)
|
||||
|
||||
type admin_info map[string]interface{}
|
||||
|
||||
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
|
||||
os.Exit(run())
|
||||
}
|
||||
|
@ -32,6 +36,7 @@ func main() {
|
|||
func run() int {
|
||||
logbuffer := &bytes.Buffer{}
|
||||
logger := log.New(logbuffer, "", log.Flags())
|
||||
|
||||
defer func() int {
|
||||
if r := recover(); r != nil {
|
||||
logger.Println("Fatal error:", r)
|
||||
|
@ -41,83 +46,30 @@ func run() int {
|
|||
return 0
|
||||
}()
|
||||
|
||||
endpoint := defaults.GetDefaults().DefaultAdminListen
|
||||
cmdLineEnv := newCmdLineEnv()
|
||||
cmdLineEnv.parseFlagsAndArgs()
|
||||
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] command [key=value] [key=value] ...\n\n", os.Args[0])
|
||||
fmt.Println("Options:")
|
||||
flag.PrintDefaults()
|
||||
fmt.Println()
|
||||
fmt.Println("Please note that options must always specified BEFORE the command\non the command line or they will be ignored.")
|
||||
fmt.Println()
|
||||
fmt.Println("Commands:\n - Use \"list\" for a list of available commands")
|
||||
fmt.Println()
|
||||
fmt.Println("Examples:")
|
||||
fmt.Println(" - ", os.Args[0], "list")
|
||||
fmt.Println(" - ", os.Args[0], "getPeers")
|
||||
fmt.Println(" - ", os.Args[0], "-v getSelf")
|
||||
fmt.Println(" - ", os.Args[0], "setTunTap name=auto mtu=1500 tap_mode=false")
|
||||
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", endpoint, "Admin socket endpoint")
|
||||
injson := flag.Bool("json", false, "Output in JSON format (as opposed to pretty-print)")
|
||||
verbose := flag.Bool("v", false, "Verbose output (includes public keys)")
|
||||
ver := flag.Bool("version", false, "Prints the version of this build")
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
|
||||
if *ver {
|
||||
if cmdLineEnv.ver {
|
||||
fmt.Println("Build name:", version.BuildName())
|
||||
fmt.Println("Build version:", version.BuildVersion())
|
||||
fmt.Println("To get the version number of the running Yggdrasil node, run", os.Args[0], "getSelf")
|
||||
return 0
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
if len(cmdLineEnv.args) == 0 {
|
||||
flag.Usage()
|
||||
return 0
|
||||
}
|
||||
|
||||
if *server == endpoint {
|
||||
if config, err := ioutil.ReadFile(defaults.GetDefaults().DefaultConfigFile); err == nil {
|
||||
if bytes.Equal(config[0:2], []byte{0xFF, 0xFE}) ||
|
||||
bytes.Equal(config[0:2], []byte{0xFE, 0xFF}) {
|
||||
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
|
||||
decoder := utf.NewDecoder()
|
||||
config, err = decoder.Bytes(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
var dat map[string]interface{}
|
||||
if err := hjson.Unmarshal(config, &dat); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if ep, ok := dat["AdminListen"].(string); ok && (ep != "none" && ep != "") {
|
||||
endpoint = ep
|
||||
logger.Println("Found platform default config file", defaults.GetDefaults().DefaultConfigFile)
|
||||
logger.Println("Using endpoint", endpoint, "from AdminListen")
|
||||
} else {
|
||||
logger.Println("Configuration file doesn't contain appropriate AdminListen option")
|
||||
logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen)
|
||||
}
|
||||
} else {
|
||||
logger.Println("Can't open config file from default location", defaults.GetDefaults().DefaultConfigFile)
|
||||
logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen)
|
||||
}
|
||||
} else {
|
||||
endpoint = *server
|
||||
logger.Println("Using endpoint", endpoint, "from command line")
|
||||
}
|
||||
cmdLineEnv.setEndpoint(logger)
|
||||
|
||||
var conn net.Conn
|
||||
u, err := url.Parse(endpoint)
|
||||
u, err := url.Parse(cmdLineEnv.endpoint)
|
||||
if err == nil {
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "unix":
|
||||
logger.Println("Connecting to UNIX socket", endpoint[7:])
|
||||
conn, err = net.Dial("unix", endpoint[7:])
|
||||
logger.Println("Connecting to UNIX socket", cmdLineEnv.endpoint[7:])
|
||||
conn, err = net.Dial("unix", cmdLineEnv.endpoint[7:])
|
||||
case "tcp":
|
||||
logger.Println("Connecting to TCP socket", u.Host)
|
||||
conn, err = net.Dial("tcp", u.Host)
|
||||
|
@ -127,326 +79,255 @@ func run() int {
|
|||
}
|
||||
} else {
|
||||
logger.Println("Connecting to TCP socket", u.Host)
|
||||
conn, err = net.Dial("tcp", endpoint)
|
||||
conn, err = net.Dial("tcp", cmdLineEnv.endpoint)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// config and socket are done, work without unprivileges
|
||||
if err := protect.Pledge("stdio"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
logger.Println("Connected")
|
||||
defer conn.Close()
|
||||
|
||||
decoder := json.NewDecoder(conn)
|
||||
encoder := json.NewEncoder(conn)
|
||||
send := make(admin_info)
|
||||
recv := make(admin_info)
|
||||
|
||||
for c, a := range args {
|
||||
send := &admin.AdminSocketRequest{}
|
||||
recv := &admin.AdminSocketResponse{}
|
||||
args := map[string]string{}
|
||||
for c, a := range cmdLineEnv.args {
|
||||
if c == 0 {
|
||||
if strings.HasPrefix(a, "-") {
|
||||
logger.Printf("Ignoring flag %s as it should be specified before other parameters\n", a)
|
||||
continue
|
||||
}
|
||||
logger.Printf("Sending request: %v\n", a)
|
||||
send["request"] = a
|
||||
send.Name = a
|
||||
continue
|
||||
}
|
||||
tokens := strings.Split(a, "=")
|
||||
if len(tokens) == 1 {
|
||||
send[tokens[0]] = true
|
||||
} else if len(tokens) > 2 {
|
||||
send[tokens[0]] = strings.Join(tokens[1:], "=")
|
||||
} else if len(tokens) == 2 {
|
||||
if i, err := strconv.Atoi(tokens[1]); err == nil {
|
||||
logger.Printf("Sending parameter %s: %d\n", tokens[0], i)
|
||||
send[tokens[0]] = i
|
||||
} else {
|
||||
switch strings.ToLower(tokens[1]) {
|
||||
case "true":
|
||||
send[tokens[0]] = true
|
||||
case "false":
|
||||
send[tokens[0]] = false
|
||||
default:
|
||||
send[tokens[0]] = tokens[1]
|
||||
}
|
||||
logger.Printf("Sending parameter %s: %v\n", tokens[0], send[tokens[0]])
|
||||
}
|
||||
tokens := strings.SplitN(a, "=", 2)
|
||||
switch {
|
||||
case len(tokens) == 1:
|
||||
logger.Println("Ignoring invalid argument:", a)
|
||||
default:
|
||||
args[tokens[0]] = tokens[1]
|
||||
}
|
||||
}
|
||||
|
||||
if send.Arguments, err = json.Marshal(args); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := encoder.Encode(&send); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logger.Printf("Request sent")
|
||||
if err := decoder.Decode(&recv); err == nil {
|
||||
logger.Printf("Response received")
|
||||
if recv["status"] == "error" {
|
||||
if err, ok := recv["error"]; ok {
|
||||
fmt.Println("Admin socket returned an error:", err)
|
||||
} else {
|
||||
fmt.Println("Admin socket returned an error but didn't specify any error text")
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if _, ok := recv["request"]; !ok {
|
||||
fmt.Println("Missing request in response (malformed response?)")
|
||||
return 1
|
||||
}
|
||||
if _, ok := recv["response"]; !ok {
|
||||
fmt.Println("Missing response body (malformed response?)")
|
||||
return 1
|
||||
}
|
||||
req := recv["request"].(map[string]interface{})
|
||||
res := recv["response"].(map[string]interface{})
|
||||
|
||||
if *injson {
|
||||
if json, err := json.MarshalIndent(res, "", " "); err == nil {
|
||||
fmt.Println(string(json))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
switch strings.ToLower(req["request"].(string)) {
|
||||
case "dot":
|
||||
fmt.Println(res["dot"])
|
||||
case "list", "getpeers", "getswitchpeers", "getdht", "getsessions", "dhtping":
|
||||
maxWidths := make(map[string]int)
|
||||
var keyOrder []string
|
||||
keysOrdered := false
|
||||
|
||||
for _, tlv := range res {
|
||||
for slk, slv := range tlv.(map[string]interface{}) {
|
||||
if !keysOrdered {
|
||||
for k := range slv.(map[string]interface{}) {
|
||||
if !*verbose {
|
||||
if k == "box_pub_key" || k == "box_sig_key" || k == "nodeinfo" || k == "was_mtu_fixed" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
keyOrder = append(keyOrder, fmt.Sprint(k))
|
||||
}
|
||||
sort.Strings(keyOrder)
|
||||
keysOrdered = true
|
||||
}
|
||||
for k, v := range slv.(map[string]interface{}) {
|
||||
if len(fmt.Sprint(slk)) > maxWidths["key"] {
|
||||
maxWidths["key"] = len(fmt.Sprint(slk))
|
||||
}
|
||||
if len(fmt.Sprint(v)) > maxWidths[k] {
|
||||
maxWidths[k] = len(fmt.Sprint(v))
|
||||
if maxWidths[k] < len(k) {
|
||||
maxWidths[k] = len(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(keyOrder) > 0 {
|
||||
fmt.Printf("%-"+fmt.Sprint(maxWidths["key"])+"s ", "")
|
||||
for _, v := range keyOrder {
|
||||
fmt.Printf("%-"+fmt.Sprint(maxWidths[v])+"s ", v)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
for slk, slv := range tlv.(map[string]interface{}) {
|
||||
fmt.Printf("%-"+fmt.Sprint(maxWidths["key"])+"s ", slk)
|
||||
for _, k := range keyOrder {
|
||||
preformatted := slv.(map[string]interface{})[k]
|
||||
var formatted string
|
||||
switch k {
|
||||
case "bytes_sent", "bytes_recvd":
|
||||
formatted = fmt.Sprintf("%d", uint(preformatted.(float64)))
|
||||
case "uptime", "last_seen":
|
||||
seconds := uint(preformatted.(float64)) % 60
|
||||
minutes := uint(preformatted.(float64)/60) % 60
|
||||
hours := uint(preformatted.(float64) / 60 / 60)
|
||||
formatted = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
|
||||
default:
|
||||
formatted = fmt.Sprint(preformatted)
|
||||
}
|
||||
fmt.Printf("%-"+fmt.Sprint(maxWidths[k])+"s ", formatted)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
case "gettuntap", "settuntap":
|
||||
for k, v := range res {
|
||||
fmt.Println("Interface name:", k)
|
||||
if mtu, ok := v.(map[string]interface{})["mtu"].(float64); ok {
|
||||
fmt.Println("Interface MTU:", mtu)
|
||||
}
|
||||
if tap_mode, ok := v.(map[string]interface{})["tap_mode"].(bool); ok {
|
||||
fmt.Println("TAP mode:", tap_mode)
|
||||
}
|
||||
}
|
||||
case "getself":
|
||||
for k, v := range res["self"].(map[string]interface{}) {
|
||||
if buildname, ok := v.(map[string]interface{})["build_name"].(string); ok && buildname != "unknown" {
|
||||
fmt.Println("Build name:", buildname)
|
||||
}
|
||||
if buildversion, ok := v.(map[string]interface{})["build_version"].(string); ok && buildversion != "unknown" {
|
||||
fmt.Println("Build version:", buildversion)
|
||||
}
|
||||
fmt.Println("IPv6 address:", k)
|
||||
if subnet, ok := v.(map[string]interface{})["subnet"].(string); ok {
|
||||
fmt.Println("IPv6 subnet:", subnet)
|
||||
}
|
||||
if boxSigKey, ok := v.(map[string]interface{})["key"].(string); ok {
|
||||
fmt.Println("Public key:", boxSigKey)
|
||||
}
|
||||
if coords, ok := v.(map[string]interface{})["coords"].(string); ok {
|
||||
fmt.Println("Coords:", coords)
|
||||
}
|
||||
if *verbose {
|
||||
if nodeID, ok := v.(map[string]interface{})["node_id"].(string); ok {
|
||||
fmt.Println("Node ID:", nodeID)
|
||||
}
|
||||
if boxPubKey, ok := v.(map[string]interface{})["box_pub_key"].(string); ok {
|
||||
fmt.Println("Public encryption key:", boxPubKey)
|
||||
}
|
||||
if boxSigKey, ok := v.(map[string]interface{})["box_sig_key"].(string); ok {
|
||||
fmt.Println("Public signing key:", boxSigKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "getswitchqueues":
|
||||
maximumqueuesize := float64(4194304)
|
||||
portqueues := make(map[float64]float64)
|
||||
portqueuesize := make(map[float64]float64)
|
||||
portqueuepackets := make(map[float64]float64)
|
||||
v := res["switchqueues"].(map[string]interface{})
|
||||
if queuecount, ok := v["queues_count"].(float64); ok {
|
||||
fmt.Printf("Active queue count: %d queues\n", uint(queuecount))
|
||||
}
|
||||
if queuesize, ok := v["queues_size"].(float64); ok {
|
||||
fmt.Printf("Active queue size: %d bytes\n", uint(queuesize))
|
||||
}
|
||||
if highestqueuecount, ok := v["highest_queues_count"].(float64); ok {
|
||||
fmt.Printf("Highest queue count: %d queues\n", uint(highestqueuecount))
|
||||
}
|
||||
if highestqueuesize, ok := v["highest_queues_size"].(float64); ok {
|
||||
fmt.Printf("Highest queue size: %d bytes\n", uint(highestqueuesize))
|
||||
}
|
||||
if m, ok := v["maximum_queues_size"].(float64); ok {
|
||||
maximumqueuesize = m
|
||||
fmt.Printf("Maximum queue size: %d bytes\n", uint(maximumqueuesize))
|
||||
}
|
||||
if queues, ok := v["queues"].([]interface{}); ok {
|
||||
if len(queues) != 0 {
|
||||
fmt.Println("Active queues:")
|
||||
for _, v := range queues {
|
||||
queueport := v.(map[string]interface{})["queue_port"].(float64)
|
||||
queuesize := v.(map[string]interface{})["queue_size"].(float64)
|
||||
queuepackets := v.(map[string]interface{})["queue_packets"].(float64)
|
||||
queueid := v.(map[string]interface{})["queue_id"].(string)
|
||||
portqueues[queueport]++
|
||||
portqueuesize[queueport] += queuesize
|
||||
portqueuepackets[queueport] += queuepackets
|
||||
queuesizepercent := (100 / maximumqueuesize) * queuesize
|
||||
fmt.Printf("- Switch port %d, Stream ID: %v, size: %d bytes (%d%% full), %d packets\n",
|
||||
uint(queueport), []byte(queueid), uint(queuesize),
|
||||
uint(queuesizepercent), uint(queuepackets))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(portqueuesize) > 0 && len(portqueuepackets) > 0 {
|
||||
fmt.Println("Aggregated statistics by switchport:")
|
||||
for k, v := range portqueuesize {
|
||||
queuesizepercent := (100 / (portqueues[k] * maximumqueuesize)) * v
|
||||
fmt.Printf("- Switch port %d, size: %d bytes (%d%% full), %d packets\n",
|
||||
uint(k), uint(v), uint(queuesizepercent), uint(portqueuepackets[k]))
|
||||
}
|
||||
}
|
||||
case "addpeer", "removepeer", "addallowedencryptionpublickey", "removeallowedencryptionpublickey", "addsourcesubnet", "addroute", "removesourcesubnet", "removeroute":
|
||||
if _, ok := res["added"]; ok {
|
||||
for _, v := range res["added"].([]interface{}) {
|
||||
fmt.Println("Added:", fmt.Sprint(v))
|
||||
}
|
||||
}
|
||||
if _, ok := res["not_added"]; ok {
|
||||
for _, v := range res["not_added"].([]interface{}) {
|
||||
fmt.Println("Not added:", fmt.Sprint(v))
|
||||
}
|
||||
}
|
||||
if _, ok := res["removed"]; ok {
|
||||
for _, v := range res["removed"].([]interface{}) {
|
||||
fmt.Println("Removed:", fmt.Sprint(v))
|
||||
}
|
||||
}
|
||||
if _, ok := res["not_removed"]; ok {
|
||||
for _, v := range res["not_removed"].([]interface{}) {
|
||||
fmt.Println("Not removed:", fmt.Sprint(v))
|
||||
}
|
||||
}
|
||||
case "getallowedencryptionpublickeys":
|
||||
if _, ok := res["allowed_box_pubs"]; !ok {
|
||||
fmt.Println("All connections are allowed")
|
||||
} else if res["allowed_box_pubs"] == nil {
|
||||
fmt.Println("All connections are allowed")
|
||||
} else {
|
||||
fmt.Println("Connections are allowed only from the following public box keys:")
|
||||
for _, v := range res["allowed_box_pubs"].([]interface{}) {
|
||||
fmt.Println("-", v)
|
||||
}
|
||||
}
|
||||
case "getmulticastinterfaces":
|
||||
if _, ok := res["multicast_interfaces"]; !ok {
|
||||
fmt.Println("No multicast interfaces found")
|
||||
} else if res["multicast_interfaces"] == nil {
|
||||
fmt.Println("No multicast interfaces found")
|
||||
} else {
|
||||
fmt.Println("Multicast peer discovery is active on:")
|
||||
for _, v := range res["multicast_interfaces"].([]interface{}) {
|
||||
fmt.Println("-", v)
|
||||
}
|
||||
}
|
||||
case "getsourcesubnets":
|
||||
if _, ok := res["source_subnets"]; !ok {
|
||||
fmt.Println("No source subnets found")
|
||||
} else if res["source_subnets"] == nil {
|
||||
fmt.Println("No source subnets found")
|
||||
} else {
|
||||
fmt.Println("Source subnets:")
|
||||
for _, v := range res["source_subnets"].([]interface{}) {
|
||||
fmt.Println("-", v)
|
||||
}
|
||||
}
|
||||
case "getroutes":
|
||||
if routes, ok := res["routes"].(map[string]interface{}); !ok {
|
||||
fmt.Println("No routes found")
|
||||
} else {
|
||||
if res["routes"] == nil || len(routes) == 0 {
|
||||
fmt.Println("No routes found")
|
||||
} else {
|
||||
fmt.Println("Routes:")
|
||||
for k, v := range routes {
|
||||
if pv, ok := v.(string); ok {
|
||||
fmt.Println("-", k, " via ", pv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "settunnelrouting":
|
||||
fallthrough
|
||||
case "gettunnelrouting":
|
||||
if enabled, ok := res["enabled"].(bool); !ok {
|
||||
fmt.Println("Tunnel routing is disabled")
|
||||
} else if !enabled {
|
||||
fmt.Println("Tunnel routing is disabled")
|
||||
} else {
|
||||
fmt.Println("Tunnel routing is enabled")
|
||||
}
|
||||
default:
|
||||
if json, err := json.MarshalIndent(recv["response"], "", " "); err == nil {
|
||||
fmt.Println(string(json))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.Println("Error receiving response:", err)
|
||||
if err := decoder.Decode(&recv); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if v, ok := recv["status"]; ok && v != "success" {
|
||||
if recv.Status == "error" {
|
||||
if err := recv.Error; err != "" {
|
||||
fmt.Println("Admin socket returned an error:", err)
|
||||
} else {
|
||||
fmt.Println("Admin socket returned an error but didn't specify any error text")
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if cmdLineEnv.injson {
|
||||
if json, err := json.MarshalIndent(recv.Response, "", " "); err == nil {
|
||||
fmt.Println(string(json))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.SetAutoFormatHeaders(false)
|
||||
table.SetCenterSeparator("")
|
||||
table.SetColumnSeparator("")
|
||||
table.SetRowSeparator("")
|
||||
table.SetHeaderLine(false)
|
||||
table.SetBorder(false)
|
||||
table.SetTablePadding("\t") // pad with tabs
|
||||
table.SetNoWhiteSpace(true)
|
||||
table.SetAutoWrapText(false)
|
||||
|
||||
switch strings.ToLower(send.Name) {
|
||||
case "list":
|
||||
var resp admin.ListResponse
|
||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
table.SetHeader([]string{"Command", "Arguments", "Description"})
|
||||
for _, entry := range resp.List {
|
||||
for i := range entry.Fields {
|
||||
entry.Fields[i] = entry.Fields[i] + "=..."
|
||||
}
|
||||
table.Append([]string{entry.Command, strings.Join(entry.Fields, ", "), entry.Description})
|
||||
}
|
||||
table.Render()
|
||||
|
||||
case "getself":
|
||||
var resp admin.GetSelfResponse
|
||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
table.Append([]string{"Build name:", resp.BuildName})
|
||||
table.Append([]string{"Build version:", resp.BuildVersion})
|
||||
table.Append([]string{"IPv6 address:", resp.IPAddress})
|
||||
table.Append([]string{"IPv6 subnet:", resp.Subnet})
|
||||
table.Append([]string{"Routing table size:", fmt.Sprintf("%d", resp.RoutingEntries)})
|
||||
table.Append([]string{"Public key:", resp.PublicKey})
|
||||
table.Render()
|
||||
|
||||
case "getpeers":
|
||||
var resp admin.GetPeersResponse
|
||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
table.SetHeader([]string{"URI", "State", "Dir", "IP Address", "Uptime", "RTT", "RX", "TX", "Down", "Up", "Pr", "Cost", "Last Error"})
|
||||
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{
|
||||
uristring,
|
||||
state,
|
||||
dir,
|
||||
peer.IPAddress,
|
||||
(time.Duration(peer.Uptime) * time.Second).String(),
|
||||
rtt,
|
||||
peer.RXBytes.String(),
|
||||
peer.TXBytes.String(),
|
||||
rxr,
|
||||
txr,
|
||||
fmt.Sprintf("%d", peer.Priority),
|
||||
fmt.Sprintf("%d", peer.Cost),
|
||||
lasterr,
|
||||
})
|
||||
}
|
||||
table.Render()
|
||||
|
||||
case "gettree":
|
||||
var resp admin.GetTreeResponse
|
||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//table.SetHeader([]string{"Public Key", "IP Address", "Port", "Rest"})
|
||||
table.SetHeader([]string{"Public Key", "IP Address", "Parent", "Sequence"})
|
||||
for _, tree := range resp.Tree {
|
||||
table.Append([]string{
|
||||
tree.PublicKey,
|
||||
tree.IPAddress,
|
||||
tree.Parent,
|
||||
fmt.Sprintf("%d", tree.Sequence),
|
||||
//fmt.Sprintf("%d", dht.Port),
|
||||
//fmt.Sprintf("%d", dht.Rest),
|
||||
})
|
||||
}
|
||||
table.Render()
|
||||
|
||||
case "getpaths":
|
||||
var resp admin.GetPathsResponse
|
||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
table.SetHeader([]string{"Public Key", "IP Address", "Path", "Seq"})
|
||||
for _, p := range resp.Paths {
|
||||
table.Append([]string{
|
||||
p.PublicKey,
|
||||
p.IPAddress,
|
||||
fmt.Sprintf("%v", p.Path),
|
||||
fmt.Sprintf("%d", p.Sequence),
|
||||
})
|
||||
}
|
||||
table.Render()
|
||||
|
||||
case "getsessions":
|
||||
var resp admin.GetSessionsResponse
|
||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
table.SetHeader([]string{"Public Key", "IP Address", "Uptime", "RX", "TX"})
|
||||
for _, p := range resp.Sessions {
|
||||
table.Append([]string{
|
||||
p.PublicKey,
|
||||
p.IPAddress,
|
||||
(time.Duration(p.Uptime) * time.Second).String(),
|
||||
p.RXBytes.String(),
|
||||
p.TXBytes.String(),
|
||||
})
|
||||
}
|
||||
table.Render()
|
||||
|
||||
case "getnodeinfo":
|
||||
var resp core.GetNodeInfoResponse
|
||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, v := range resp {
|
||||
fmt.Println(string(v))
|
||||
break
|
||||
}
|
||||
|
||||
case "getmulticastinterfaces":
|
||||
var resp multicast.GetMulticastInterfacesResponse
|
||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmtBool := func(b bool) string {
|
||||
if b {
|
||||
return "Yes"
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
table.SetHeader([]string{"Name", "Listen Address", "Beacon", "Listen", "Password"})
|
||||
for _, p := range resp.Interfaces {
|
||||
table.Append([]string{
|
||||
p.Name,
|
||||
p.Address,
|
||||
fmtBool(p.Beacon),
|
||||
fmtBool(p.Listen),
|
||||
fmtBool(p.Password),
|
||||
})
|
||||
}
|
||||
table.Render()
|
||||
|
||||
case "gettun":
|
||||
var resp tun.GetTUNResponse
|
||||
if err := json.Unmarshal(recv.Response, &resp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
table.Append([]string{"TUN enabled:", fmt.Sprintf("%#v", resp.Enabled)})
|
||||
if resp.Enabled {
|
||||
table.Append([]string{"Interface name:", resp.Name})
|
||||
table.Append([]string{"Interface MTU:", fmt.Sprintf("%d", resp.MTU)})
|
||||
}
|
||||
table.Render()
|
||||
|
||||
case "addpeer", "removepeer":
|
||||
|
||||
default:
|
||||
fmt.Println(string(recv.Response))
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
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/)
|
||||
|
||||
*/
|
||||
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
|
||||
fi
|
||||
|
||||
if [ $PKGARCH = "amd64" ]; then GOARCH=amd64 GOOS=linux ./build
|
||||
elif [ $PKGARCH = "i386" ]; then GOARCH=386 GOOS=linux ./build
|
||||
elif [ $PKGARCH = "mipsel" ]; then GOARCH=mipsle GOOS=linux ./build
|
||||
elif [ $PKGARCH = "mips" ]; then GOARCH=mips64 GOOS=linux ./build
|
||||
elif [ $PKGARCH = "armhf" ]; then GOARCH=arm GOOS=linux GOARM=6 ./build
|
||||
elif [ $PKGARCH = "arm64" ]; then GOARCH=arm64 GOOS=linux ./build
|
||||
elif [ $PKGARCH = "armel" ]; then GOARCH=arm GOOS=linux GOARM=5 ./build
|
||||
GOLDFLAGS="-X github.com/yggdrasil-network/yggdrasil-go/src/config.defaultConfig=/etc/yggdrasil/yggdrasil.conf"
|
||||
GOLDFLAGS="${GOLDFLAGS} -X github.com/yggdrasil-network/yggdrasil-go/src/config.defaultAdminListen=unix:///var/run/yggdrasil/yggdrasil.sock"
|
||||
|
||||
if [ $PKGARCH = "amd64" ]; then GOARCH=amd64 GOOS=linux ./build -l "${GOLDFLAGS}"
|
||||
elif [ $PKGARCH = "i386" ]; then GOARCH=386 GOOS=linux ./build -l "${GOLDFLAGS}"
|
||||
elif [ $PKGARCH = "mipsel" ]; then GOARCH=mipsle GOOS=linux ./build -l "${GOLDFLAGS}"
|
||||
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
|
||||
echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf,arm64,armel"
|
||||
exit 1
|
||||
|
@ -38,7 +41,7 @@ echo "Building $PKGFILE"
|
|||
mkdir -p /tmp/$PKGNAME/
|
||||
mkdir -p /tmp/$PKGNAME/debian/
|
||||
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
|
||||
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
|
||||
Package: $PKGNAME
|
||||
Version: $PKGVERSION
|
||||
Section: contrib/net
|
||||
Priority: extra
|
||||
Section: golang
|
||||
Priority: optional
|
||||
Architecture: $PKGARCH
|
||||
Replaces: $PKGREPLACES
|
||||
Conflicts: $PKGREPLACES
|
||||
Depends: systemd
|
||||
Maintainer: Neil Alexander <neilalexander@users.noreply.github.com>
|
||||
Description: Yggdrasil Network
|
||||
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
|
||||
usr/bin/yggdrasil usr/bin
|
||||
usr/bin/yggdrasilctl usr/bin
|
||||
etc/systemd/system/*.service etc/systemd/system
|
||||
lib/systemd/system/*.service lib/systemd/system
|
||||
EOF
|
||||
cat > /tmp/$PKGNAME/debian/postinst << EOF
|
||||
#!/bin/sh
|
||||
|
||||
systemctl daemon-reload
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
mkdir -p /var/backups
|
||||
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`
|
||||
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
|
||||
cp /etc/yggdrasil/yggdrasil.conf /var/backups/yggdrasil.conf.`date +%Y%m%d`
|
||||
|
||||
if command -v systemctl >/dev/null; then
|
||||
systemctl daemon-reload >/dev/null || true
|
||||
systemctl enable yggdrasil || true
|
||||
systemctl start yggdrasil || true
|
||||
fi
|
||||
echo "Normalising and updating /etc/yggdrasil/yggdrasil.conf"
|
||||
/usr/bin/yggdrasil -useconf -normaliseconf < /var/backups/yggdrasil.conf.`date +%Y%m%d` > /etc/yggdrasil/yggdrasil.conf
|
||||
|
||||
chown root:yggdrasil /etc/yggdrasil/yggdrasil.conf
|
||||
chmod 640 /etc/yggdrasil/yggdrasil.conf
|
||||
else
|
||||
echo "Generating initial configuration file /etc/yggdrasil.conf"
|
||||
echo "Please familiarise yourself with this file before starting Yggdrasil"
|
||||
sh -c 'umask 0027 && /usr/bin/yggdrasil -genconf > /etc/yggdrasil.conf'
|
||||
chgrp yggdrasil /etc/yggdrasil.conf
|
||||
echo "Generating initial configuration file /etc/yggdrasil/yggdrasil.conf"
|
||||
/usr/bin/yggdrasil -genconf > /etc/yggdrasil/yggdrasil.conf
|
||||
|
||||
chown root:yggdrasil /etc/yggdrasil/yggdrasil.conf
|
||||
chmod 640 /etc/yggdrasil/yggdrasil.conf
|
||||
fi
|
||||
|
||||
systemctl enable yggdrasil
|
||||
systemctl restart yggdrasil
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
cat > /tmp/$PKGNAME/debian/prerm << EOF
|
||||
#!/bin/sh
|
||||
|
@ -110,13 +131,14 @@ EOF
|
|||
|
||||
cp yggdrasil /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 \
|
||||
etc/systemd/system/yggdrasil.service \
|
||||
etc/systemd/system/yggdrasil-default-config.service
|
||||
tar -czvf /tmp/$PKGNAME/control.tar.gz -C /tmp/$PKGNAME/debian .
|
||||
lib/systemd/system/yggdrasil.service \
|
||||
lib/systemd/system/yggdrasil-default-config.service
|
||||
tar --no-xattrs -czvf /tmp/$PKGNAME/control.tar.gz -C /tmp/$PKGNAME/debian .
|
||||
echo 2.0 > /tmp/$PKGNAME/debian-binary
|
||||
|
||||
ar -r $PKGFILE \
|
||||
|
|
|
@ -15,6 +15,10 @@ command -v mkbom >/dev/null 2>&1 || (
|
|||
sudo make install || (echo "Failed to build mkbom"; exit 1)
|
||||
)
|
||||
|
||||
# Build Yggdrasil
|
||||
echo "running GO111MODULE=on GOOS=darwin GOARCH=${PKGARCH-amd64} ./build"
|
||||
GO111MODULE=on GOOS=darwin GOARCH=${PKGARCH-amd64} ./build
|
||||
|
||||
# Check if we can find the files we need - they should
|
||||
# exist if you are running this script from the root of
|
||||
# the yggdrasil-go repo and you have ran ./build
|
||||
|
@ -75,6 +79,7 @@ PKGNAME=$(sh contrib/semver/name.sh)
|
|||
PKGVERSION=$(sh contrib/semver/version.sh --bare)
|
||||
PKGARCH=${PKGARCH-amd64}
|
||||
PAYLOADSIZE=$(( $(wc -c pkgbuild/flat/base.pkg/Payload | awk '{ print $1 }') / 1024 ))
|
||||
[ "$PKGARCH" = "amd64" ] && PKGHOSTARCH="x86_64" || PKGHOSTARCH=${PKGARCH}
|
||||
|
||||
# Create the PackageInfo file
|
||||
cat > pkgbuild/flat/base.pkg/PackageInfo << EOF
|
||||
|
@ -94,7 +99,7 @@ cat > pkgbuild/flat/Distribution << EOF
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<installer-script minSpecVersion="1.000000" authoringTool="com.apple.PackageMaker" authoringToolVersion="3.0.3" authoringToolBuild="174">
|
||||
<title>Yggdrasil (${PKGNAME}-${PKGVERSION})</title>
|
||||
<options customize="never" allow-external-scripts="no"/>
|
||||
<options customize="never" allow-external-scripts="no" hostArchitectures="${PKGHOSTARCH}" />
|
||||
<domains enable_anywhere="true"/>
|
||||
<installation-check script="pm_install_check();"/>
|
||||
<script>
|
||||
|
|
62
contrib/mobile/build
Executable file
62
contrib/mobile/build
Executable file
|
@ -0,0 +1,62 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -ef
|
||||
|
||||
[ ! -d contrib/mobile ] && (echo "Must run ./contrib/mobile/build [-i] [-a] from the repository top level folder"; exit 1)
|
||||
|
||||
PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/version}
|
||||
PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)}
|
||||
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"
|
||||
ARGS="-v"
|
||||
|
||||
while getopts "aitc:l:d" option
|
||||
do
|
||||
case "$option"
|
||||
in
|
||||
i) IOS=true;;
|
||||
a) ANDROID=true;;
|
||||
t) TABLES=true;;
|
||||
c) GCFLAGS="$GCFLAGS $OPTARG";;
|
||||
l) LDFLAGS="$LDFLAGS $OPTARG";;
|
||||
d) ARGS="$ARGS -tags debug" DEBUG=true;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z $TABLES ] && [ -z $DEBUG ]; then
|
||||
LDFLAGS="$LDFLAGS -s -w"
|
||||
fi
|
||||
|
||||
if [ ! $IOS ] && [ ! $ANDROID ]; then
|
||||
echo "Must specify -a (Android), -i (iOS) or both"
|
||||
exit 1
|
||||
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
|
||||
echo "Building framework for iOS"
|
||||
go get golang.org/x/mobile/bind
|
||||
gomobile bind \
|
||||
-target ios,macos -tags mobile -o Yggdrasil.xcframework \
|
||||
-ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \
|
||||
./contrib/mobile ./src/config;
|
||||
fi
|
||||
|
||||
if [ $ANDROID ]; then
|
||||
echo "Building aar for Android"
|
||||
go get golang.org/x/mobile/bind
|
||||
gomobile bind \
|
||||
-target android -tags mobile -o yggdrasil.aar \
|
||||
-ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \
|
||||
./contrib/mobile ./src/config;
|
||||
fi
|
301
contrib/mobile/mobile.go
Normal file
301
contrib/mobile/mobile.go
Normal file
|
@ -0,0 +1,301 @@
|
|||
package mobile
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"regexp"
|
||||
|
||||
"github.com/gologme/log"
|
||||
|
||||
"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/ipv6rwc"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/tun"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/version"
|
||||
)
|
||||
|
||||
// Yggdrasil mobile package is meant to "plug the gap" for mobile support, as
|
||||
// Gomobile will not create headers for Swift/Obj-C etc if they have complex
|
||||
// (non-native) types. Therefore for iOS we will expose some nice simple
|
||||
// functions. Note that in the case of iOS we handle reading/writing to/from TUN
|
||||
// in Swift therefore we use the "dummy" TUN interface instead.
|
||||
type Yggdrasil struct {
|
||||
core *core.Core
|
||||
iprwc *ipv6rwc.ReadWriteCloser
|
||||
config *config.NodeConfig
|
||||
multicast *multicast.Multicast
|
||||
tun *tun.TunAdapter // optional
|
||||
log MobileLogger
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// StartAutoconfigure starts a node with a randomly generated config
|
||||
func (m *Yggdrasil) StartAutoconfigure() error {
|
||||
return m.StartJSON([]byte("{}"))
|
||||
}
|
||||
|
||||
// StartJSON starts a node with the given JSON config. You can get JSON config
|
||||
// (rather than HJSON) by using the GenerateConfigJSON() function
|
||||
func (m *Yggdrasil) StartJSON(configjson []byte) error {
|
||||
setMemLimitIfPossible()
|
||||
|
||||
logger := log.New(m.log, "", 0)
|
||||
logger.EnableLevel("error")
|
||||
logger.EnableLevel("warn")
|
||||
logger.EnableLevel("info")
|
||||
m.logger = logger
|
||||
m.config = config.GenerateConfig()
|
||||
if err := m.config.UnmarshalHJSON(configjson); err != nil {
|
||||
return err
|
||||
}
|
||||
// Set up the Yggdrasil node itself.
|
||||
{
|
||||
iprange := net.IPNet{
|
||||
IP: net.ParseIP("200::"),
|
||||
Mask: net.CIDRMask(7, 128),
|
||||
}
|
||||
options := []core.SetupOption{
|
||||
core.PeerFilter(func(ip net.IP) bool {
|
||||
return !iprange.Contains(ip)
|
||||
}),
|
||||
}
|
||||
for _, peer := range m.config.Peers {
|
||||
options = append(options, core.Peer{URI: peer})
|
||||
}
|
||||
for intf, peers := range m.config.InterfacePeers {
|
||||
for _, peer := range peers {
|
||||
options = append(options, core.Peer{URI: peer, SourceInterface: intf})
|
||||
}
|
||||
}
|
||||
for _, allowed := range m.config.AllowedPublicKeys {
|
||||
k, err := hex.DecodeString(allowed)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
options = append(options, core.AllowedPublicKey(k[:]))
|
||||
}
|
||||
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 {
|
||||
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())
|
||||
}
|
||||
|
||||
// Set up the multicast module.
|
||||
if len(m.config.MulticastInterfaces) > 0 {
|
||||
var err error
|
||||
logger.Infof("Initializing multicast %s", "")
|
||||
options := []multicast.SetupOption{}
|
||||
for _, intf := range m.config.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,
|
||||
})
|
||||
}
|
||||
logger.Infof("Starting multicast %s", "")
|
||||
m.multicast, err = multicast.New(m.core, m.logger, options...)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred starting multicast:", err)
|
||||
}
|
||||
}
|
||||
|
||||
mtu := m.config.IfMTU
|
||||
m.iprwc = ipv6rwc.NewReadWriteCloser(m.core)
|
||||
if m.iprwc.MaxMTU() < mtu {
|
||||
mtu = m.iprwc.MaxMTU()
|
||||
}
|
||||
m.iprwc.SetMTU(mtu)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send sends a packet to Yggdrasil. It should be a fully formed
|
||||
// IPv6 packet
|
||||
func (m *Yggdrasil) Send(p []byte) error {
|
||||
if m.iprwc == nil {
|
||||
return nil
|
||||
}
|
||||
_, _ = m.iprwc.Write(p)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send sends a packet from given buffer to Yggdrasil. From first byte up to length.
|
||||
func (m *Yggdrasil) SendBuffer(p []byte, length int) error {
|
||||
if m.iprwc == nil {
|
||||
return nil
|
||||
}
|
||||
if len(p) < length {
|
||||
return nil
|
||||
}
|
||||
_, _ = m.iprwc.Write(p[:length])
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recv waits for and reads a packet coming from Yggdrasil. It
|
||||
// will be a fully formed IPv6 packet
|
||||
func (m *Yggdrasil) Recv() ([]byte, error) {
|
||||
if m.iprwc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var buf [65535]byte
|
||||
n, _ := m.iprwc.Read(buf[:])
|
||||
return buf[:n], nil
|
||||
}
|
||||
|
||||
// Recv waits for and reads a packet coming from Yggdrasil to given buffer, returning size of packet
|
||||
func (m *Yggdrasil) RecvBuffer(buf []byte) (int, error) {
|
||||
if m.iprwc == nil {
|
||||
return 0, nil
|
||||
}
|
||||
n, _ := m.iprwc.Read(buf)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Stop the mobile Yggdrasil instance
|
||||
func (m *Yggdrasil) Stop() error {
|
||||
logger := log.New(m.log, "", 0)
|
||||
logger.EnableLevel("info")
|
||||
logger.Infof("Stopping the mobile Yggdrasil instance %s", "")
|
||||
if m.multicast != nil {
|
||||
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()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retry resets the peer connection timer and tries to dial them immediately.
|
||||
func (m *Yggdrasil) RetryPeersNow() {
|
||||
m.core.RetryPeersNow()
|
||||
}
|
||||
|
||||
// GenerateConfigJSON generates mobile-friendly configuration in JSON format
|
||||
func GenerateConfigJSON() []byte {
|
||||
nc := config.GenerateConfig()
|
||||
nc.IfName = "none"
|
||||
if json, err := json.Marshal(nc); err == nil {
|
||||
return json
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAddressString gets the node's IPv6 address
|
||||
func (m *Yggdrasil) GetAddressString() string {
|
||||
ip := m.core.Address()
|
||||
return ip.String()
|
||||
}
|
||||
|
||||
// GetSubnetString gets the node's IPv6 subnet in CIDR notation
|
||||
func (m *Yggdrasil) GetSubnetString() string {
|
||||
subnet := m.core.Subnet()
|
||||
return subnet.String()
|
||||
}
|
||||
|
||||
// GetPublicKeyString gets the node's public key in hex form
|
||||
func (m *Yggdrasil) GetPublicKeyString() string {
|
||||
return hex.EncodeToString(m.core.GetSelf().Key)
|
||||
}
|
||||
|
||||
// GetRoutingEntries gets the number of entries in the routing table
|
||||
func (m *Yggdrasil) GetRoutingEntries() int {
|
||||
return int(m.core.GetSelf().RoutingEntries)
|
||||
}
|
||||
|
||||
func (m *Yggdrasil) GetPeersJSON() (result string) {
|
||||
peers := []struct {
|
||||
core.PeerInfo
|
||||
IP string
|
||||
}{}
|
||||
for _, v := range m.core.GetPeers() {
|
||||
var ip string
|
||||
if v.Key != nil {
|
||||
a := address.AddrForKey(v.Key)
|
||||
ip = net.IP(a[:]).String()
|
||||
}
|
||||
peers = append(peers, struct {
|
||||
core.PeerInfo
|
||||
IP string
|
||||
}{
|
||||
PeerInfo: v,
|
||||
IP: ip,
|
||||
})
|
||||
}
|
||||
if res, err := json.Marshal(peers); err == nil {
|
||||
return string(res)
|
||||
} else {
|
||||
return "{}"
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Yggdrasil) GetPathsJSON() (result string) {
|
||||
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)
|
||||
} else {
|
||||
return "{}"
|
||||
}
|
||||
}
|
||||
|
||||
// GetMTU returns the configured node MTU. This must be called AFTER Start.
|
||||
func (m *Yggdrasil) GetMTU() int {
|
||||
return int(m.core.MTU())
|
||||
}
|
||||
|
||||
func GetVersion() string {
|
||||
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(),
|
||||
}
|
||||
}
|
13
contrib/mobile/mobile_android.go
Normal file
13
contrib/mobile/mobile_android.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
//go:build android
|
||||
// +build android
|
||||
|
||||
package mobile
|
||||
|
||||
import "log"
|
||||
|
||||
type MobileLogger struct{}
|
||||
|
||||
func (nsl MobileLogger) Write(p []byte) (n int, err error) {
|
||||
log.Println(string(p))
|
||||
return len(p), nil
|
||||
}
|
40
contrib/mobile/mobile_apple.go
Normal file
40
contrib/mobile/mobile_apple.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
//go:build ios || darwin
|
||||
// +build ios darwin
|
||||
|
||||
package mobile
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation
|
||||
#import <Foundation/Foundation.h>
|
||||
void Log(const char *text) {
|
||||
NSString *nss = [NSString stringWithUTF8String:text];
|
||||
NSLog(@"%@", nss);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/tun"
|
||||
)
|
||||
|
||||
type MobileLogger struct {
|
||||
}
|
||||
|
||||
func (nsl MobileLogger) Write(p []byte) (n int, err error) {
|
||||
p = append(p, 0)
|
||||
cstr := (*C.char)(unsafe.Pointer(&p[0]))
|
||||
C.Log(cstr)
|
||||
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
|
||||
}
|
14
contrib/mobile/mobile_other.go
Normal file
14
contrib/mobile/mobile_other.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
//go:build !android && !ios && !darwin
|
||||
// +build !android,!ios,!darwin
|
||||
|
||||
package mobile
|
||||
|
||||
import "fmt"
|
||||
|
||||
type MobileLogger struct {
|
||||
}
|
||||
|
||||
func (nsl MobileLogger) Write(p []byte) (n int, err error) {
|
||||
fmt.Print(string(p))
|
||||
return len(p), nil
|
||||
}
|
28
contrib/mobile/mobile_test.go
Normal file
28
contrib/mobile/mobile_test.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package mobile
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gologme/log"
|
||||
)
|
||||
|
||||
func TestStartYggdrasil(t *testing.T) {
|
||||
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 {
|
||||
t.Fatalf("Failed to start Yggdrasil: %s", err)
|
||||
}
|
||||
t.Log("Address:", ygg.GetAddressString())
|
||||
t.Log("Subnet:", ygg.GetSubnetString())
|
||||
t.Log("Routing entries:", ygg.GetRoutingEntries())
|
||||
if err := ygg.Stop(); err != nil {
|
||||
t.Fatalf("Failed to stop Yggdrasil: %s", err)
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
# This script generates an MSI file for Yggdrasil for a given architecture. It
|
||||
# needs to run on Windows within MSYS2 and Go 1.13 or later must be installed on
|
||||
# the system and within the PATH. This is ran currently by Appveyor (see
|
||||
# appveyor.yml in the repository root) for both x86 and x64.
|
||||
# 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 workflows in the repository).
|
||||
#
|
||||
# Author: Neil Alexander <neilalexander@users.noreply.github.com>
|
||||
|
||||
|
@ -11,47 +11,18 @@
|
|||
PKGARCH=$1
|
||||
if [ "${PKGARCH}" == "" ];
|
||||
then
|
||||
echo "tell me the architecture: x86, x64 or arm"
|
||||
echo "tell me the architecture: x86, x64, arm or arm64"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the rest of the repository history. This is needed within Appveyor because
|
||||
# otherwise we don't get all of the branch histories and therefore the semver
|
||||
# scripts don't work properly.
|
||||
if [ "${APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH}" != "" ];
|
||||
then
|
||||
git fetch --all
|
||||
git checkout ${APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH}
|
||||
elif [ "${APPVEYOR_REPO_BRANCH}" != "" ];
|
||||
then
|
||||
git fetch --all
|
||||
git checkout ${APPVEYOR_REPO_BRANCH}
|
||||
fi
|
||||
|
||||
# Install prerequisites within MSYS2
|
||||
pacman -S --needed --noconfirm unzip git curl
|
||||
|
||||
# Download the wix tools!
|
||||
if [ ! -d wixbin ];
|
||||
then
|
||||
curl -LO https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip
|
||||
if [ `md5sum wix311-binaries.zip | cut -f 1 -d " "` != "47a506f8ab6666ee3cc502fb07d0ee2a" ];
|
||||
then
|
||||
echo "wix package didn't match expected checksum"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p wixbin
|
||||
unzip -o wix311-binaries.zip -d wixbin || (
|
||||
echo "failed to unzip WiX"
|
||||
exit 1
|
||||
)
|
||||
fi
|
||||
dotnet tool install --global wix --version 5.0.0
|
||||
|
||||
# Build Yggdrasil!
|
||||
[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build
|
||||
[ "${PKGARCH}" == "x86" ] && GOOS=windows GOARCH=386 CGO_ENABLED=0 ./build
|
||||
[ "${PKGARCH}" == "arm" ] && GOOS=windows GOARCH=arm CGO_ENABLED=0 ./build
|
||||
#[ "${PKGARCH}" == "arm64" ] && GOOS=windows GOARCH=arm64 CGO_ENABLED=0 ./build
|
||||
[ "${PKGARCH}" == "arm64" ] && GOOS=windows GOARCH=arm64 CGO_ENABLED=0 ./build
|
||||
|
||||
# Create the postinstall script
|
||||
cat > updateconfig.bat << EOF
|
||||
|
@ -69,14 +40,19 @@ EOF
|
|||
PKGNAME=$(sh contrib/semver/name.sh)
|
||||
PKGVERSION=$(sh contrib/msi/msversion.sh --bare)
|
||||
PKGVERSIONMS=$(echo $PKGVERSION | tr - .)
|
||||
[ "${PKGARCH}" == "x64" ] && \
|
||||
([ "${PKGARCH}" == "x64" ] || [ "${PKGARCH}" == "arm64" ]) && \
|
||||
PKGGUID="77757838-1a23-40a5-a720-c3b43e0260cc" PKGINSTFOLDER="ProgramFiles64Folder" || \
|
||||
PKGGUID="54a3294e-a441-4322-aefb-3bb40dd022bb" PKGINSTFOLDER="ProgramFilesFolder"
|
||||
|
||||
# Download the Wintun driver
|
||||
if [ ! -d wintun ];
|
||||
then
|
||||
curl -o wintun.zip https://www.wintun.net/builds/wintun-0.11.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
|
||||
fi
|
||||
if [ $PKGARCH = "x64" ]; then
|
||||
|
@ -85,8 +61,8 @@ elif [ $PKGARCH = "x86" ]; then
|
|||
PKGWINTUNDLL=wintun/bin/x86/wintun.dll
|
||||
elif [ $PKGARCH = "arm" ]; then
|
||||
PKGWINTUNDLL=wintun/bin/arm/wintun.dll
|
||||
#elif [ $PKGARCH = "arm64" ]; then
|
||||
# PKGWINTUNDLL=wintun/bin/arm64/wintun.dll
|
||||
elif [ $PKGARCH = "arm64" ]; then
|
||||
PKGWINTUNDLL=wintun/bin/arm64/wintun.dll
|
||||
else
|
||||
echo "wasn't sure which architecture to get wintun for"
|
||||
exit 1
|
||||
|
@ -117,7 +93,7 @@ cat > wix.xml << EOF
|
|||
Description="Yggdrasil Network Installer"
|
||||
Comments="Yggdrasil Network standalone router for Windows."
|
||||
Manufacturer="github.com/yggdrasil-network"
|
||||
InstallerVersion="200"
|
||||
InstallerVersion="500"
|
||||
InstallScope="perMachine"
|
||||
Languages="1033"
|
||||
Compressed="yes"
|
||||
|
@ -221,5 +197,5 @@ EOF
|
|||
# Generate the MSI
|
||||
CANDLEFLAGS="-nologo"
|
||||
LIGHTFLAGS="-nologo -spdb -sice:ICE71 -sice:ICE61"
|
||||
wixbin/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
|
||||
candle $CANDLEFLAGS -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj -arch ${PKGARCH} wix.xml && \
|
||||
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"
|
||||
|
||||
command="/usr/bin/yggdrasil"
|
||||
extra_started_commands="reload"
|
||||
|
||||
depend() {
|
||||
use net dns logger
|
||||
|
@ -42,12 +41,6 @@ start() {
|
|||
eend $?
|
||||
}
|
||||
|
||||
reload() {
|
||||
ebegin "Reloading ${RC_SVCNAME}"
|
||||
start-stop-daemon --signal HUP --pidfile "${pidfile}"
|
||||
eend $?
|
||||
}
|
||||
|
||||
stop() {
|
||||
ebegin "Stopping ${RC_SVCNAME}"
|
||||
start-stop-daemon --stop --pidfile "${pidfile}" --exec "${command}"
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Get the current branch name
|
||||
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
|
||||
BRANCH="$GITHUB_REF_NAME"
|
||||
if [ -z "$BRANCH" ]; then
|
||||
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
|
||||
fi
|
||||
|
||||
# Complain if the git history is not available
|
||||
if [ $? != 0 ] || [ -z "$BRANCH" ]; then
|
||||
printf "yggdrasil"
|
||||
exit 0
|
||||
|
|
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
|
|
@ -1,8 +1,8 @@
|
|||
[Unit]
|
||||
Description=yggdrasil
|
||||
Wants=network.target
|
||||
Wants=network-online.target
|
||||
Wants=yggdrasil-default-config.service
|
||||
After=network.target
|
||||
After=network-online.target
|
||||
After=yggdrasil-default-config.service
|
||||
|
||||
[Service]
|
||||
|
@ -10,7 +10,7 @@ Group=yggdrasil
|
|||
ProtectHome=true
|
||||
ProtectSystem=true
|
||||
SyslogIdentifier=yggdrasil
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
ExecStartPre=+-/sbin/modprobe tun
|
||||
ExecStart=/usr/bin/yggdrasil -useconffile /etc/yggdrasil.conf
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
|
|
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
|
63
go.mod
63
go.mod
|
@ -1,26 +1,49 @@
|
|||
module github.com/yggdrasil-network/yggdrasil-go
|
||||
|
||||
go 1.16
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/Arceliar/ironwood v0.0.0-20210619124114-6ad55cae5031
|
||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/cheggaaa/pb/v3 v3.0.8
|
||||
github.com/fatih/color v1.12.0 // indirect
|
||||
github.com/gologme/log v1.2.0
|
||||
github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d
|
||||
github.com/cheggaaa/pb/v3 v3.1.5
|
||||
github.com/coder/websocket v1.8.12
|
||||
github.com/gologme/log v1.3.0
|
||||
github.com/hashicorp/go-syslog v1.0.0
|
||||
github.com/hjson/hjson-go v3.1.0+incompatible
|
||||
github.com/kardianos/minwinsvc v1.0.0
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b
|
||||
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273
|
||||
golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2
|
||||
golang.zx2c4.com/wireguard/windows v0.3.14
|
||||
github.com/hjson/hjson-go/v4 v4.4.0
|
||||
github.com/kardianos/minwinsvc v1.0.2
|
||||
github.com/quic-go/quic-go v0.48.2
|
||||
github.com/vishvananda/netlink v1.3.0
|
||||
github.com/wlynxg/anet v0.0.5
|
||||
golang.org/x/crypto v0.33.0
|
||||
golang.org/x/net v0.35.0
|
||||
golang.org/x/sys v0.30.0
|
||||
golang.org/x/text v0.22.0
|
||||
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 (
|
||||
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
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // 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 (
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
suah.dev/protect v1.2.4
|
||||
)
|
||||
|
|
186
go.sum
186
go.sum
|
@ -1,79 +1,119 @@
|
|||
github.com/Arceliar/ironwood v0.0.0-20210619124114-6ad55cae5031 h1:DZVDfYhVdu+0wAiRHoY1olyNkKxIot9UjBnbQFzuUlM=
|
||||
github.com/Arceliar/ironwood v0.0.0-20210619124114-6ad55cae5031/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk=
|
||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ=
|
||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI=
|
||||
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
||||
github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3 h1:d8N0z+udAnbU5PdjpLSNPTWlqeU/nnYsQ42B6+879aw=
|
||||
github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3/go.mod h1:SrrElc3FFMpYCODSr11jWbLFeOM8WsY+DbDY/l2AXF0=
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM=
|
||||
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q=
|
||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||
github.com/cheggaaa/pb/v3 v3.0.8 h1:bC8oemdChbke2FHIIGy9mn4DPJ2caZYQnfbRqwmdCoA=
|
||||
github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c=
|
||||
github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
|
||||
github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
||||
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bits-and-blooms/bloom/v3 v3.7.0 h1:VfknkqV4xI+PsaDIsoHueyxVDZrfvMn56jeWUzvzdls=
|
||||
github.com/bits-and-blooms/bloom/v3 v3.7.0/go.mod h1:VKlUSvp0lFIYqxJjzdnSsZEw4iHb1kOL2tfHTgyJBHg=
|
||||
github.com/cheggaaa/pb/v3 v3.1.5 h1:QuuUzeM2WsAqG2gMqtzaWithDJv0i+i6UlnwSCI4QLk=
|
||||
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/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hjson/hjson-go v3.1.0+incompatible h1:DY/9yE8ey8Zv22bY+mHV1uk2yRy0h8tKhZ77hEdi0Aw=
|
||||
github.com/hjson/hjson-go v3.1.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
|
||||
github.com/kardianos/minwinsvc v1.0.0 h1:+JfAi8IBJna0jY2dJGZqi7o15z13JelFIklJCAENALA=
|
||||
github.com/kardianos/minwinsvc v1.0.0/go.mod h1:Bgd0oc+D0Qo3bBytmNtyRKVlp85dAloLKhfxanPFFRc=
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
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.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
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/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/hjson/hjson-go/v4 v4.4.0 h1:D/NPvqOCH6/eisTb5/ztuIS8GUvmpHaLOcNk1Bjr298=
|
||||
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/go.mod h1:LUZNYhNmxujx2tR7FbdxqYJ9XDDoCd3MQcl1o//FWl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
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/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/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 h1:faDu4veV+8pcThn4fewv6TVlNCezafGoC1gM/mxQLbQ=
|
||||
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
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-0.20210503195748-5c7c50ebbd4f h1:yQJrRE0hDxDFmZLlRaw+3vusO4fwNHgHIjUOMO7bHYI=
|
||||
golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210510202332-9844c74f67ec/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2 h1:wfOOSvHgIzTZ9h5Vb6yUFZNn7uf3bT7PeYsHOO7tYDM=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
|
||||
golang.zx2c4.com/wireguard/windows v0.3.14 h1:5yIDYyrQyGkLqV+tzY4ilMNeIvQeMXAz0glZz9u179A=
|
||||
golang.zx2c4.com/wireguard/windows v0.3.14/go.mod h1:3P4IEAsb+BjlKZmpUXgy74c0iX9AVwwr3WcVJ8nPgME=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
|
||||
github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
|
||||
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
||||
suah.dev/protect v1.2.4 h1:iVZG/zQB63FKNpITDYM/cXoAeCTIjCiXHuFVByJFDzg=
|
||||
suah.dev/protect v1.2.4/go.mod h1:vVrquYO3u1Ep9Ez2z8x+6N6/czm+TBmWKZfiXU2tb54=
|
||||
|
|
|
@ -64,7 +64,7 @@ func AddrForKey(publicKey ed25519.PublicKey) *Address {
|
|||
buf[idx] = ^buf[idx]
|
||||
}
|
||||
var addr Address
|
||||
var temp []byte
|
||||
var temp = make([]byte, 0, 32)
|
||||
done := false
|
||||
ones := byte(0)
|
||||
bits := byte(0)
|
||||
|
@ -108,16 +108,16 @@ func SubnetForKey(publicKey ed25519.PublicKey) *Subnet {
|
|||
}
|
||||
var snet Subnet
|
||||
copy(snet[:], addr[:])
|
||||
prefix := GetPrefix()
|
||||
prefix := GetPrefix() // nolint:staticcheck
|
||||
snet[len(prefix)-1] |= 0x01
|
||||
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.
|
||||
func (a *Address) GetKey() ed25519.PublicKey {
|
||||
var key [ed25519.PublicKeySize]byte
|
||||
prefix := GetPrefix()
|
||||
prefix := GetPrefix() // nolint:staticcheck
|
||||
ones := int(a[len(prefix)])
|
||||
for idx := 0; idx < ones; idx++ {
|
||||
key[idx/8] |= 0x80 >> byte(idx%8)
|
||||
|
@ -129,7 +129,11 @@ func (a *Address) GetKey() ed25519.PublicKey {
|
|||
bits <<= byte(idx % 8)
|
||||
keyIdx := keyOffset + (idx - addrOffset)
|
||||
bits >>= byte(keyIdx % 8)
|
||||
key[keyIdx/8] |= bits
|
||||
idx := keyIdx / 8
|
||||
if idx >= len(key) {
|
||||
break
|
||||
}
|
||||
key[idx] |= bits
|
||||
}
|
||||
for idx := range key {
|
||||
key[idx] = ^key[idx]
|
||||
|
@ -137,7 +141,7 @@ func (a *Address) GetKey() ed25519.PublicKey {
|
|||
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.
|
||||
func (s *Subnet) GetKey() ed25519.PublicKey {
|
||||
var addr Address
|
||||
|
|
114
src/address/address_test.go
Normal file
114
src/address/address_test.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
package address
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAddress_Address_IsValid(t *testing.T) {
|
||||
var address Address
|
||||
_, _ = rand.Read(address[:])
|
||||
|
||||
address[0] = 0
|
||||
|
||||
if address.IsValid() {
|
||||
t.Fatal("invalid address marked as valid")
|
||||
}
|
||||
|
||||
address[0] = 0x03
|
||||
|
||||
if address.IsValid() {
|
||||
t.Fatal("invalid address marked as valid")
|
||||
}
|
||||
|
||||
address[0] = 0x02
|
||||
|
||||
if !address.IsValid() {
|
||||
t.Fatal("valid address marked as invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddress_Subnet_IsValid(t *testing.T) {
|
||||
var subnet Subnet
|
||||
_, _ = rand.Read(subnet[:])
|
||||
|
||||
subnet[0] = 0
|
||||
|
||||
if subnet.IsValid() {
|
||||
t.Fatal("invalid subnet marked as valid")
|
||||
}
|
||||
|
||||
subnet[0] = 0x02
|
||||
|
||||
if subnet.IsValid() {
|
||||
t.Fatal("invalid subnet marked as valid")
|
||||
}
|
||||
|
||||
subnet[0] = 0x03
|
||||
|
||||
if !subnet.IsValid() {
|
||||
t.Fatal("valid subnet marked as invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddress_AddrForKey(t *testing.T) {
|
||||
publicKey := ed25519.PublicKey{
|
||||
189, 186, 207, 216, 34, 64, 222, 61, 205, 18, 57, 36, 203, 181, 82, 86,
|
||||
251, 141, 171, 8, 170, 152, 227, 5, 82, 138, 184, 79, 65, 158, 110, 251,
|
||||
}
|
||||
|
||||
expectedAddress := Address{
|
||||
2, 0, 132, 138, 96, 79, 187, 126, 67, 132, 101, 219, 141, 182, 104, 149,
|
||||
}
|
||||
|
||||
if *AddrForKey(publicKey) != expectedAddress {
|
||||
t.Fatal("invalid address returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddress_SubnetForKey(t *testing.T) {
|
||||
publicKey := ed25519.PublicKey{
|
||||
189, 186, 207, 216, 34, 64, 222, 61, 205, 18, 57, 36, 203, 181, 82, 86,
|
||||
251, 141, 171, 8, 170, 152, 227, 5, 82, 138, 184, 79, 65, 158, 110, 251,
|
||||
}
|
||||
|
||||
expectedSubnet := Subnet{3, 0, 132, 138, 96, 79, 187, 126}
|
||||
|
||||
if *SubnetForKey(publicKey) != expectedSubnet {
|
||||
t.Fatal("invalid subnet returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddress_Address_GetKey(t *testing.T) {
|
||||
address := Address{
|
||||
2, 0, 132, 138, 96, 79, 187, 126, 67, 132, 101, 219, 141, 182, 104, 149,
|
||||
}
|
||||
|
||||
expectedPublicKey := ed25519.PublicKey{
|
||||
189, 186, 207, 216, 34, 64, 222, 61,
|
||||
205, 18, 57, 36, 203, 181, 127, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
}
|
||||
|
||||
if !bytes.Equal(address.GetKey(), expectedPublicKey) {
|
||||
t.Fatal("invalid public key returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddress_Subnet_GetKey(t *testing.T) {
|
||||
subnet := Subnet{3, 0, 132, 138, 96, 79, 187, 126}
|
||||
|
||||
expectedPublicKey := ed25519.PublicKey{
|
||||
189, 186, 207, 216, 34, 64, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
}
|
||||
|
||||
if !bytes.Equal(subnet.GetKey(), expectedPublicKey) {
|
||||
t.Fatal("invalid public key returned")
|
||||
}
|
||||
}
|
21
src/admin/addpeer.go
Normal file
21
src/admin/addpeer.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type AddPeerRequest struct {
|
||||
Uri string `json:"uri"`
|
||||
Sintf string `json:"interface,omitempty"`
|
||||
}
|
||||
|
||||
type AddPeerResponse struct{}
|
||||
|
||||
func (a *AdminSocket) addPeerHandler(req *AddPeerRequest, _ *AddPeerResponse) error {
|
||||
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)
|
||||
}
|
|
@ -7,55 +7,63 @@ import (
|
|||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gologme/log"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||
)
|
||||
|
||||
// TODO: Add authentication
|
||||
|
||||
type AdminSocket struct {
|
||||
core *core.Core
|
||||
log *log.Logger
|
||||
listenaddr string
|
||||
listener net.Listener
|
||||
handlers map[string]handler
|
||||
done chan struct{}
|
||||
core *core.Core
|
||||
log core.Logger
|
||||
listener net.Listener
|
||||
handlers map[string]handler
|
||||
done chan struct{}
|
||||
config struct {
|
||||
listenaddr ListenAddress
|
||||
}
|
||||
}
|
||||
|
||||
type AdminSocketRequest struct {
|
||||
Name string `json:"request"`
|
||||
Arguments json.RawMessage `json:"arguments,omitempty"`
|
||||
KeepAlive bool `json:"keepalive,omitempty"`
|
||||
}
|
||||
|
||||
type AdminSocketResponse struct {
|
||||
Status string `json:"status"`
|
||||
Request struct {
|
||||
Name string `json:"request"`
|
||||
KeepAlive bool `json:"keepalive"`
|
||||
} `json:"request"`
|
||||
Response interface{} `json:"response"`
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Request AdminSocketRequest `json:"request"`
|
||||
Response json.RawMessage `json:"response"`
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
args []string // List of human-readable argument names
|
||||
handler func(json.RawMessage) (interface{}, error) // First is input map, second is output
|
||||
desc string // What does the endpoint do?
|
||||
args []string // List of human-readable argument names
|
||||
handler core.AddHandlerFunc // First is input map, second is output
|
||||
}
|
||||
|
||||
type ListResponse struct {
|
||||
List map[string]ListEntry `json:"list"`
|
||||
List []ListEntry `json:"list"`
|
||||
}
|
||||
|
||||
type ListEntry struct {
|
||||
Fields []string `json:"fields"`
|
||||
Command string `json:"command"`
|
||||
Description string `json:"description"`
|
||||
Fields []string `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
// AddHandler is called for each admin function to add the handler and help documentation to the API.
|
||||
func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(json.RawMessage) (interface{}, error)) error {
|
||||
func (a *AdminSocket) AddHandler(name, desc string, args []string, handlerfunc core.AddHandlerFunc) error {
|
||||
if _, ok := a.handlers[strings.ToLower(name)]; ok {
|
||||
return errors.New("handler already exists")
|
||||
}
|
||||
a.handlers[strings.ToLower(name)] = handler{
|
||||
desc: desc,
|
||||
args: args,
|
||||
handler: handlerfunc,
|
||||
}
|
||||
|
@ -63,99 +71,182 @@ func (a *AdminSocket) AddHandler(name string, args []string, handlerfunc func(js
|
|||
}
|
||||
|
||||
// Init runs the initial admin setup.
|
||||
func (a *AdminSocket) Init(c *core.Core, nc *config.NodeConfig, log *log.Logger, options interface{}) error {
|
||||
a.core = c
|
||||
a.log = log
|
||||
a.handlers = make(map[string]handler)
|
||||
nc.RLock()
|
||||
a.listenaddr = nc.AdminListen
|
||||
nc.RUnlock()
|
||||
a.done = make(chan struct{})
|
||||
close(a.done) // Start in a done / not-started state
|
||||
_ = a.AddHandler("list", []string{}, func(_ json.RawMessage) (interface{}, error) {
|
||||
res := &ListResponse{
|
||||
List: map[string]ListEntry{},
|
||||
}
|
||||
for name, handler := range a.handlers {
|
||||
res.List[name] = ListEntry{
|
||||
Fields: handler.args,
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
})
|
||||
a.core.SetAdmin(a)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AdminSocket) SetupAdminHandlers(na *AdminSocket) {
|
||||
_ = a.AddHandler("getSelf", []string{}, func(in json.RawMessage) (interface{}, error) {
|
||||
req := &GetSelfRequest{}
|
||||
res := &GetSelfResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := a.getSelfHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
})
|
||||
_ = a.AddHandler("getPeers", []string{}, func(in json.RawMessage) (interface{}, error) {
|
||||
req := &GetPeersRequest{}
|
||||
res := &GetPeersResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := a.getPeersHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
})
|
||||
_ = a.AddHandler("getDHT", []string{}, func(in json.RawMessage) (interface{}, error) {
|
||||
req := &GetDHTRequest{}
|
||||
res := &GetDHTResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := a.getDHTHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
})
|
||||
_ = a.AddHandler("getPaths", []string{}, func(in json.RawMessage) (interface{}, error) {
|
||||
req := &GetPathsRequest{}
|
||||
res := &GetPathsResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := a.getPathsHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
})
|
||||
_ = a.AddHandler("getSessions", []string{}, func(in json.RawMessage) (interface{}, error) {
|
||||
req := &GetSessionsRequest{}
|
||||
res := &GetSessionsResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := a.getSessionsHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
// Start runs the admin API socket to listen for / respond to admin API calls.
|
||||
func (a *AdminSocket) Start() error {
|
||||
if a.listenaddr != "none" && a.listenaddr != "" {
|
||||
a.done = make(chan struct{})
|
||||
go a.listen()
|
||||
func New(c *core.Core, log core.Logger, opts ...SetupOption) (*AdminSocket, error) {
|
||||
a := &AdminSocket{
|
||||
core: c,
|
||||
log: log,
|
||||
handlers: make(map[string]handler),
|
||||
}
|
||||
return nil
|
||||
for _, opt := range opts {
|
||||
a._applyOption(opt)
|
||||
}
|
||||
if a.config.listenaddr == "none" || a.config.listenaddr == "" {
|
||||
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) {
|
||||
res := &ListResponse{}
|
||||
for name, handler := range a.handlers {
|
||||
res.List = append(res.List, ListEntry{
|
||||
Command: name,
|
||||
Description: handler.desc,
|
||||
Fields: handler.args,
|
||||
})
|
||||
}
|
||||
sort.SliceStable(res.List, func(i, j int) bool {
|
||||
return strings.Compare(res.List[i].Command, res.List[j].Command) < 0
|
||||
})
|
||||
return res, nil
|
||||
})
|
||||
a.done = make(chan struct{})
|
||||
go a.listen()
|
||||
return a, a.core.SetAdmin(a)
|
||||
}
|
||||
|
||||
func (a *AdminSocket) SetupAdminHandlers() {
|
||||
_ = a.AddHandler(
|
||||
"getSelf", "Show details about this node", []string{},
|
||||
func(in json.RawMessage) (interface{}, error) {
|
||||
req := &GetSelfRequest{}
|
||||
res := &GetSelfResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := a.getSelfHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
},
|
||||
)
|
||||
_ = a.AddHandler(
|
||||
"getPeers", "Show directly connected peers", []string{},
|
||||
func(in json.RawMessage) (interface{}, error) {
|
||||
req := &GetPeersRequest{}
|
||||
res := &GetPeersResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := a.getPeersHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
},
|
||||
)
|
||||
_ = a.AddHandler(
|
||||
"getTree", "Show known Tree entries", []string{},
|
||||
func(in json.RawMessage) (interface{}, error) {
|
||||
req := &GetTreeRequest{}
|
||||
res := &GetTreeResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := a.getTreeHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
},
|
||||
)
|
||||
_ = a.AddHandler(
|
||||
"getPaths", "Show established paths through this node", []string{},
|
||||
func(in json.RawMessage) (interface{}, error) {
|
||||
req := &GetPathsRequest{}
|
||||
res := &GetPathsResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := a.getPathsHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
},
|
||||
)
|
||||
_ = a.AddHandler(
|
||||
"getSessions", "Show established traffic sessions with remote nodes", []string{},
|
||||
func(in json.RawMessage) (interface{}, error) {
|
||||
req := &GetSessionsRequest{}
|
||||
res := &GetSessionsResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := a.getSessionsHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
},
|
||||
)
|
||||
_ = a.AddHandler(
|
||||
"addPeer", "Add a peer to the peer list", []string{"uri", "interface"},
|
||||
func(in json.RawMessage) (interface{}, error) {
|
||||
req := &AddPeerRequest{}
|
||||
res := &AddPeerResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := a.addPeerHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
},
|
||||
)
|
||||
_ = a.AddHandler(
|
||||
"removePeer", "Remove a peer from the peer list", []string{"uri", "interface"},
|
||||
func(in json.RawMessage) (interface{}, error) {
|
||||
req := &RemovePeerRequest{}
|
||||
res := &RemovePeerResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := a.removePeerHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// IsStarted returns true if the module has been started.
|
||||
|
@ -172,6 +263,9 @@ func (a *AdminSocket) IsStarted() bool {
|
|||
|
||||
// Stop will stop the admin API and close the socket.
|
||||
func (a *AdminSocket) Stop() error {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
if a.listener != nil {
|
||||
select {
|
||||
case <-a.done:
|
||||
|
@ -185,50 +279,6 @@ func (a *AdminSocket) Stop() error {
|
|||
|
||||
// listen is run by start and manages API connections.
|
||||
func (a *AdminSocket) listen() {
|
||||
u, err := url.Parse(a.listenaddr)
|
||||
if err == nil {
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "unix":
|
||||
if _, err := os.Stat(a.listenaddr[7:]); err == nil {
|
||||
a.log.Debugln("Admin socket", a.listenaddr[7:], "already exists, trying to clean up")
|
||||
if _, err := net.DialTimeout("unix", a.listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() {
|
||||
a.log.Errorln("Admin socket", a.listenaddr[7:], "already exists and is in use by another process")
|
||||
os.Exit(1)
|
||||
} else {
|
||||
if err := os.Remove(a.listenaddr[7:]); err == nil {
|
||||
a.log.Debugln(a.listenaddr[7:], "was cleaned up")
|
||||
} else {
|
||||
a.log.Errorln(a.listenaddr[7:], "already exists and was not cleaned up:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
a.listener, err = net.Listen("unix", a.listenaddr[7:])
|
||||
if err == nil {
|
||||
switch a.listenaddr[7:8] {
|
||||
case "@": // maybe abstract namespace
|
||||
default:
|
||||
if err := os.Chmod(a.listenaddr[7:], 0660); err != nil {
|
||||
a.log.Warnln("WARNING:", a.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", a.listenaddr)
|
||||
}
|
||||
} else {
|
||||
a.listener, err = net.Listen("tcp", a.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()
|
||||
for {
|
||||
conn, err := a.listener.Accept()
|
||||
|
@ -256,53 +306,65 @@ func (a *AdminSocket) handleRequest(conn net.Conn) {
|
|||
|
||||
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 {
|
||||
var err error
|
||||
var buf json.RawMessage
|
||||
_ = decoder.Decode(&buf)
|
||||
var req AdminSocketRequest
|
||||
var resp AdminSocketResponse
|
||||
resp.Status = "success"
|
||||
if err = json.Unmarshal(buf, &resp.Request); err == nil {
|
||||
if resp.Request.Name == "" {
|
||||
resp.Status = "error"
|
||||
resp.Response = &ErrorResponse{
|
||||
Error: "No request specified",
|
||||
}
|
||||
} else if h, ok := a.handlers[strings.ToLower(resp.Request.Name)]; ok {
|
||||
resp.Response, err = h.handler(buf)
|
||||
if err != nil {
|
||||
resp.Status = "error"
|
||||
resp.Response = &ErrorResponse{
|
||||
Error: err.Error(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resp.Status = "error"
|
||||
resp.Response = &ErrorResponse{
|
||||
Error: fmt.Sprintf("Unknown action '%s', try 'list' for help", resp.Request.Name),
|
||||
}
|
||||
req.Arguments = []byte("{}")
|
||||
if err := func() error {
|
||||
if err = decoder.Decode(&buf); err != nil {
|
||||
return fmt.Errorf("Failed to find request")
|
||||
}
|
||||
if err = json.Unmarshal(buf, &req); err != nil {
|
||||
return fmt.Errorf("Failed to unmarshal request")
|
||||
}
|
||||
resp.Request = req
|
||||
if req.Name == "" {
|
||||
return fmt.Errorf("No request specified")
|
||||
}
|
||||
reqname := strings.ToLower(req.Name)
|
||||
handler, ok := a.handlers[reqname]
|
||||
if !ok {
|
||||
return fmt.Errorf("Unknown action '%s', try 'list' for help", reqname)
|
||||
}
|
||||
res, err := handler.handler(req.Arguments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Response, err = json.Marshal(res); err != nil {
|
||||
return fmt.Errorf("Failed to marshal response: %w", err)
|
||||
}
|
||||
resp.Status = "success"
|
||||
return nil
|
||||
}(); err != nil {
|
||||
resp.Status = "error"
|
||||
resp.Error = err.Error()
|
||||
}
|
||||
if err = encoder.Encode(resp); err != nil {
|
||||
a.log.Debugln("Encode error:", err)
|
||||
}
|
||||
if !resp.Request.KeepAlive {
|
||||
if !req.KeepAlive {
|
||||
break
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DataUnit uint64
|
||||
|
||||
func (d DataUnit) String() string {
|
||||
switch {
|
||||
case d >= 1024*1024*1024*1024:
|
||||
return fmt.Sprintf("%2.1fTB", float64(d)/1024/1024/1024/1024)
|
||||
case d >= 1024*1024*1024:
|
||||
return fmt.Sprintf("%2.1fGB", float64(d)/1024/1024/1024)
|
||||
case d >= 1024*1024:
|
||||
return fmt.Sprintf("%2.1fMB", float64(d)/1024/1024)
|
||||
case d >= 100:
|
||||
return fmt.Sprintf("%2.1fKB", float64(d)/1024)
|
||||
default:
|
||||
return fmt.Sprintf("%dB", d)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"net"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
)
|
||||
|
||||
type GetDHTRequest struct{}
|
||||
|
||||
type GetDHTResponse struct {
|
||||
DHT map[string]DHTEntry `json:"dht"`
|
||||
}
|
||||
|
||||
type DHTEntry struct {
|
||||
PublicKey string `json:"key"`
|
||||
Port uint64 `json:"port"`
|
||||
Rest uint64 `json:"rest"`
|
||||
}
|
||||
|
||||
func (a *AdminSocket) getDHTHandler(req *GetDHTRequest, res *GetDHTResponse) error {
|
||||
res.DHT = map[string]DHTEntry{}
|
||||
for _, d := range a.core.GetDHT() {
|
||||
addr := address.AddrForKey(d.Key)
|
||||
so := net.IP(addr[:]).String()
|
||||
res.DHT[so] = DHTEntry{
|
||||
PublicKey: hex.EncodeToString(d.Key[:]),
|
||||
Port: d.Port,
|
||||
Rest: d.Rest,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -3,6 +3,8 @@ package admin
|
|||
import (
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
)
|
||||
|
@ -11,23 +13,30 @@ type GetPathsRequest struct {
|
|||
}
|
||||
|
||||
type GetPathsResponse struct {
|
||||
Paths map[string]PathEntry `json:"paths"`
|
||||
Paths []PathEntry `json:"paths"`
|
||||
}
|
||||
|
||||
type PathEntry struct {
|
||||
IPAddress string `json:"address"`
|
||||
PublicKey string `json:"key"`
|
||||
Path []uint64 `json:"path"`
|
||||
Sequence uint64 `json:"sequence"`
|
||||
}
|
||||
|
||||
func (a *AdminSocket) getPathsHandler(req *GetPathsRequest, res *GetPathsResponse) error {
|
||||
res.Paths = map[string]PathEntry{}
|
||||
for _, p := range a.core.GetPaths() {
|
||||
func (a *AdminSocket) getPathsHandler(_ *GetPathsRequest, res *GetPathsResponse) error {
|
||||
paths := a.core.GetPaths()
|
||||
res.Paths = make([]PathEntry, 0, len(paths))
|
||||
for _, p := range paths {
|
||||
addr := address.AddrForKey(p.Key)
|
||||
so := net.IP(addr[:]).String()
|
||||
res.Paths[so] = PathEntry{
|
||||
res.Paths = append(res.Paths, PathEntry{
|
||||
IPAddress: net.IP(addr[:]).String(),
|
||||
PublicKey: hex.EncodeToString(p.Key),
|
||||
Path: p.Path,
|
||||
}
|
||||
Sequence: p.Sequence,
|
||||
})
|
||||
}
|
||||
slices.SortStableFunc(res.Paths, func(a, b PathEntry) int {
|
||||
return strings.Compare(a.PublicKey, b.PublicKey)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,9 @@ package admin
|
|||
import (
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
)
|
||||
|
@ -11,27 +14,78 @@ type GetPeersRequest struct {
|
|||
}
|
||||
|
||||
type GetPeersResponse struct {
|
||||
Peers map[string]PeerEntry `json:"peers"`
|
||||
Peers []PeerEntry `json:"peers"`
|
||||
}
|
||||
|
||||
type PeerEntry struct {
|
||||
PublicKey string `json:"key"`
|
||||
Port uint64 `json:"port"`
|
||||
Coords []uint64 `json:"coords"`
|
||||
Remote string `json:"remote"`
|
||||
URI string `json:"remote,omitempty"`
|
||||
Up bool `json:"up"`
|
||||
Inbound bool `json:"inbound"`
|
||||
IPAddress string `json:"address,omitempty"`
|
||||
PublicKey string `json:"key"`
|
||||
Port uint64 `json:"port"`
|
||||
Priority uint64 `json:"priority"`
|
||||
Cost uint64 `json:"cost"`
|
||||
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 {
|
||||
res.Peers = map[string]PeerEntry{}
|
||||
for _, p := range a.core.GetPeers() {
|
||||
addr := address.AddrForKey(p.Key)
|
||||
so := net.IP(addr[:]).String()
|
||||
res.Peers[so] = PeerEntry{
|
||||
PublicKey: hex.EncodeToString(p.Key),
|
||||
Port: p.Port,
|
||||
Coords: p.Coords,
|
||||
Remote: p.Remote,
|
||||
func (a *AdminSocket) getPeersHandler(_ *GetPeersRequest, res *GetPeersResponse) error {
|
||||
peers := a.core.GetPeers()
|
||||
res.Peers = make([]PeerEntry, 0, len(peers))
|
||||
for _, p := range peers {
|
||||
peer := PeerEntry{
|
||||
Port: p.Port,
|
||||
Up: p.Up,
|
||||
Inbound: p.Inbound,
|
||||
Priority: uint64(p.Priority), // can't be uint8 thanks to gobind
|
||||
Cost: p.Cost,
|
||||
URI: p.URI,
|
||||
RXBytes: DataUnit(p.RXBytes),
|
||||
TXBytes: DataUnit(p.TXBytes),
|
||||
RXRate: DataUnit(p.RXRate),
|
||||
TXRate: DataUnit(p.TXRate),
|
||||
Uptime: p.Uptime.Seconds(),
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -9,28 +9,22 @@ import (
|
|||
type GetSelfRequest struct{}
|
||||
|
||||
type GetSelfResponse struct {
|
||||
Self map[string]SelfEntry `json:"self"`
|
||||
BuildName string `json:"build_name"`
|
||||
BuildVersion string `json:"build_version"`
|
||||
PublicKey string `json:"key"`
|
||||
IPAddress string `json:"address"`
|
||||
RoutingEntries uint64 `json:"routing_entries"`
|
||||
Subnet string `json:"subnet"`
|
||||
}
|
||||
|
||||
type SelfEntry struct {
|
||||
BuildName string `json:"build_name"`
|
||||
BuildVersion string `json:"build_version"`
|
||||
PublicKey string `json:"key"`
|
||||
Coords []uint64 `json:"coords"`
|
||||
Subnet string `json:"subnet"`
|
||||
}
|
||||
|
||||
func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse) error {
|
||||
res.Self = make(map[string]SelfEntry)
|
||||
func (a *AdminSocket) getSelfHandler(_ *GetSelfRequest, res *GetSelfResponse) error {
|
||||
self := a.core.GetSelf()
|
||||
addr := a.core.Address().String()
|
||||
snet := a.core.Subnet()
|
||||
res.Self[addr] = SelfEntry{
|
||||
BuildName: version.BuildName(),
|
||||
BuildVersion: version.BuildVersion(),
|
||||
PublicKey: hex.EncodeToString(self.Key[:]),
|
||||
Subnet: snet.String(),
|
||||
Coords: self.Coords,
|
||||
}
|
||||
res.BuildName = version.BuildName()
|
||||
res.BuildVersion = version.BuildVersion()
|
||||
res.PublicKey = hex.EncodeToString(self.Key[:])
|
||||
res.IPAddress = a.core.Address().String()
|
||||
res.Subnet = snet.String()
|
||||
res.RoutingEntries = self.RoutingEntries
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package admin
|
|||
import (
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
)
|
||||
|
@ -10,21 +12,32 @@ import (
|
|||
type GetSessionsRequest struct{}
|
||||
|
||||
type GetSessionsResponse struct {
|
||||
Sessions map[string]SessionEntry `json:"sessions"`
|
||||
Sessions []SessionEntry `json:"sessions"`
|
||||
}
|
||||
|
||||
type SessionEntry struct {
|
||||
PublicKey string `json:"key"`
|
||||
IPAddress string `json:"address"`
|
||||
PublicKey string `json:"key"`
|
||||
RXBytes DataUnit `json:"bytes_recvd"`
|
||||
TXBytes DataUnit `json:"bytes_sent"`
|
||||
Uptime float64 `json:"uptime"`
|
||||
}
|
||||
|
||||
func (a *AdminSocket) getSessionsHandler(req *GetSessionsRequest, res *GetSessionsResponse) error {
|
||||
res.Sessions = map[string]SessionEntry{}
|
||||
for _, s := range a.core.GetSessions() {
|
||||
func (a *AdminSocket) getSessionsHandler(_ *GetSessionsRequest, res *GetSessionsResponse) error {
|
||||
sessions := a.core.GetSessions()
|
||||
res.Sessions = make([]SessionEntry, 0, len(sessions))
|
||||
for _, s := range sessions {
|
||||
addr := address.AddrForKey(s.Key)
|
||||
so := net.IP(addr[:]).String()
|
||||
res.Sessions[so] = SessionEntry{
|
||||
res.Sessions = append(res.Sessions, SessionEntry{
|
||||
IPAddress: net.IP(addr[:]).String(),
|
||||
PublicKey: hex.EncodeToString(s.Key[:]),
|
||||
}
|
||||
RXBytes: DataUnit(s.RXBytes),
|
||||
TXBytes: DataUnit(s.TXBytes),
|
||||
Uptime: s.Uptime.Seconds(),
|
||||
})
|
||||
}
|
||||
slices.SortStableFunc(res.Sessions, func(a, b SessionEntry) int {
|
||||
return strings.Compare(a.PublicKey, b.PublicKey)
|
||||
})
|
||||
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
|
||||
}
|
79
src/admin/options.go
Normal file
79
src/admin/options.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
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) {
|
||||
switch v := opt.(type) {
|
||||
case ListenAddress:
|
||||
c.config.listenaddr = v
|
||||
case LogLookups:
|
||||
c.logLookups()
|
||||
}
|
||||
}
|
||||
|
||||
type SetupOption interface {
|
||||
isSetupOption()
|
||||
}
|
||||
|
||||
type ListenAddress string
|
||||
|
||||
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
|
||||
},
|
||||
)
|
||||
}
|
21
src/admin/removepeer.go
Normal file
21
src/admin/removepeer.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type RemovePeerRequest struct {
|
||||
Uri string `json:"uri"`
|
||||
Sintf string `json:"interface,omitempty"`
|
||||
}
|
||||
|
||||
type RemovePeerResponse struct{}
|
||||
|
||||
func (a *AdminSocket) removePeerHandler(req *RemovePeerRequest, _ *RemovePeerResponse) error {
|
||||
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,45 +17,244 @@ configuration option that is not provided.
|
|||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"sync"
|
||||
"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
|
||||
// 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.
|
||||
type NodeConfig struct {
|
||||
sync.RWMutex `json:"-"`
|
||||
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."`
|
||||
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."`
|
||||
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."`
|
||||
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."`
|
||||
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."`
|
||||
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."`
|
||||
PublicKey string `comment:"Your public key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."`
|
||||
PrivateKey string `comment:"Your private key. DO NOT share this with anyone!"`
|
||||
PrivateKey KeyBytes `json:",omitempty" comment:"Your private key. DO NOT share this with anyone!"`
|
||||
PrivateKeyPath string `json:",omitempty" comment:"The path to your private key file in PEM format."`
|
||||
Certificate *tls.Certificate `json:"-"`
|
||||
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."`
|
||||
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\"."`
|
||||
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."`
|
||||
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."`
|
||||
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."`
|
||||
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."`
|
||||
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 {
|
||||
Regex string
|
||||
Beacon bool
|
||||
Listen bool
|
||||
Port uint16
|
||||
Regex string
|
||||
Beacon bool
|
||||
Listen bool
|
||||
Port uint16 `json:",omitempty"`
|
||||
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
|
||||
// signing keypair. The signing keys are used by the switch to derive the
|
||||
// structure of the spanning tree.
|
||||
func (cfg *NodeConfig) NewKeys() {
|
||||
spub, spriv, err := ed25519.GenerateKey(nil)
|
||||
// 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() *NodeConfig {
|
||||
// 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 {
|
||||
panic(err)
|
||||
}
|
||||
cfg.PublicKey = hex.EncodeToString(spub[:])
|
||||
cfg.PrivateKey = hex.EncodeToString(spriv[:])
|
||||
cfg.PrivateKey = KeyBytes(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
|
||||
}
|
||||
|
|
54
src/config/config_test.go
Normal file
54
src/config/config_test.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfig_Keys(t *testing.T) {
|
||||
/*
|
||||
var nodeConfig NodeConfig
|
||||
nodeConfig.NewKeys()
|
||||
|
||||
publicKey1, err := hex.DecodeString(nodeConfig.PublicKey)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("can not decode generated public key")
|
||||
}
|
||||
|
||||
if len(publicKey1) == 0 {
|
||||
t.Fatal("empty public key generated")
|
||||
}
|
||||
|
||||
privateKey1, err := hex.DecodeString(nodeConfig.PrivateKey)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("can not decode generated private key")
|
||||
}
|
||||
|
||||
if len(privateKey1) == 0 {
|
||||
t.Fatal("empty private key generated")
|
||||
}
|
||||
|
||||
nodeConfig.NewKeys()
|
||||
|
||||
publicKey2, err := hex.DecodeString(nodeConfig.PublicKey)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("can not decode generated public key")
|
||||
}
|
||||
|
||||
if bytes.Equal(publicKey2, publicKey1) {
|
||||
t.Fatal("same public key generated")
|
||||
}
|
||||
|
||||
privateKey2, err := hex.DecodeString(nodeConfig.PrivateKey)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("can not decode generated private key")
|
||||
}
|
||||
|
||||
if bytes.Equal(privateKey2, privateKey1) {
|
||||
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,10 +1,11 @@
|
|||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package defaults
|
||||
package config
|
||||
|
||||
// Sane defaults for the macOS/Darwin platform. The "default" options may be
|
||||
// may be replaced by the running configuration.
|
||||
func GetDefaults() platformDefaultParameters {
|
||||
func getDefaults() platformDefaultParameters {
|
||||
return platformDefaultParameters{
|
||||
// Admin
|
||||
DefaultAdminListen: "unix:///var/run/yggdrasil.sock",
|
||||
|
@ -16,9 +17,10 @@ func GetDefaults() platformDefaultParameters {
|
|||
DefaultMulticastInterfaces: []MulticastInterfaceConfig{
|
||||
{Regex: "en.*", Beacon: true, Listen: true},
|
||||
{Regex: "bridge.*", Beacon: true, Listen: true},
|
||||
{Regex: "awdl0", Beacon: false, Listen: false},
|
||||
},
|
||||
|
||||
// TUN/TAP
|
||||
// TUN
|
||||
MaximumIfMTU: 65535,
|
||||
DefaultIfMTU: 65535,
|
||||
DefaultIfName: "auto",
|
|
@ -1,10 +1,11 @@
|
|||
//go:build freebsd
|
||||
// +build freebsd
|
||||
|
||||
package defaults
|
||||
package config
|
||||
|
||||
// Sane defaults for the BSD platforms. The "default" options may be
|
||||
// may be replaced by the running configuration.
|
||||
func GetDefaults() platformDefaultParameters {
|
||||
func getDefaults() platformDefaultParameters {
|
||||
return platformDefaultParameters{
|
||||
// Admin
|
||||
DefaultAdminListen: "unix:///var/run/yggdrasil.sock",
|
||||
|
@ -17,7 +18,7 @@ func GetDefaults() platformDefaultParameters {
|
|||
{Regex: ".*", Beacon: true, Listen: true},
|
||||
},
|
||||
|
||||
// TUN/TAP
|
||||
// TUN
|
||||
MaximumIfMTU: 32767,
|
||||
DefaultIfMTU: 32767,
|
||||
DefaultIfName: "/dev/tun0",
|
|
@ -1,10 +1,11 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package defaults
|
||||
package config
|
||||
|
||||
// Sane defaults for the Linux platform. The "default" options may be
|
||||
// may be replaced by the running configuration.
|
||||
func GetDefaults() platformDefaultParameters {
|
||||
func getDefaults() platformDefaultParameters {
|
||||
return platformDefaultParameters{
|
||||
// Admin
|
||||
DefaultAdminListen: "unix:///var/run/yggdrasil.sock",
|
||||
|
@ -17,7 +18,7 @@ func GetDefaults() platformDefaultParameters {
|
|||
{Regex: ".*", Beacon: true, Listen: true},
|
||||
},
|
||||
|
||||
// TUN/TAP
|
||||
// TUN
|
||||
MaximumIfMTU: 65535,
|
||||
DefaultIfMTU: 65535,
|
||||
DefaultIfName: "auto",
|
|
@ -1,10 +1,11 @@
|
|||
//go:build openbsd
|
||||
// +build openbsd
|
||||
|
||||
package defaults
|
||||
package config
|
||||
|
||||
// Sane defaults for the BSD platforms. The "default" options may be
|
||||
// may be replaced by the running configuration.
|
||||
func GetDefaults() platformDefaultParameters {
|
||||
func getDefaults() platformDefaultParameters {
|
||||
return platformDefaultParameters{
|
||||
// Admin
|
||||
DefaultAdminListen: "unix:///var/run/yggdrasil.sock",
|
||||
|
@ -17,7 +18,7 @@ func GetDefaults() platformDefaultParameters {
|
|||
{Regex: ".*", Beacon: true, Listen: true},
|
||||
},
|
||||
|
||||
// TUN/TAP
|
||||
// TUN
|
||||
MaximumIfMTU: 16384,
|
||||
DefaultIfMTU: 16384,
|
||||
DefaultIfName: "tun0",
|
|
@ -1,10 +1,11 @@
|
|||
//go: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
|
||||
// may be replaced by the running configuration.
|
||||
func GetDefaults() platformDefaultParameters {
|
||||
func getDefaults() platformDefaultParameters {
|
||||
return platformDefaultParameters{
|
||||
// Admin
|
||||
DefaultAdminListen: "tcp://localhost:9001",
|
||||
|
@ -17,7 +18,7 @@ func GetDefaults() platformDefaultParameters {
|
|||
{Regex: ".*", Beacon: true, Listen: true},
|
||||
},
|
||||
|
||||
// TUN/TAP
|
||||
// TUN
|
||||
MaximumIfMTU: 65535,
|
||||
DefaultIfMTU: 65535,
|
||||
DefaultIfName: "none",
|
|
@ -1,10 +1,11 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package defaults
|
||||
package config
|
||||
|
||||
// Sane defaults for the Windows platform. The "default" options may be
|
||||
// may be replaced by the running configuration.
|
||||
func GetDefaults() platformDefaultParameters {
|
||||
func getDefaults() platformDefaultParameters {
|
||||
return platformDefaultParameters{
|
||||
// Admin
|
||||
DefaultAdminListen: "tcp://localhost:9001",
|
||||
|
@ -17,7 +18,7 @@ func GetDefaults() platformDefaultParameters {
|
|||
{Regex: ".*", Beacon: true, Listen: true},
|
||||
},
|
||||
|
||||
// TUN/TAP
|
||||
// TUN
|
||||
MaximumIfMTU: 65535,
|
||||
DefaultIfMTU: 65535,
|
||||
DefaultIfName: "Yggdrasil",
|
332
src/core/api.go
332
src/core/api.go
|
@ -2,114 +2,149 @@ package core
|
|||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
//"encoding/hex"
|
||||
"encoding/json"
|
||||
//"errors"
|
||||
//"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
//"sort"
|
||||
//"time"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gologme/log"
|
||||
"github.com/Arceliar/phony"
|
||||
|
||||
"github.com/Arceliar/ironwood/network"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
//"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
||||
//"github.com/Arceliar/phony"
|
||||
)
|
||||
|
||||
type Self struct {
|
||||
Key ed25519.PublicKey
|
||||
Root ed25519.PublicKey
|
||||
Coords []uint64
|
||||
type SelfInfo struct {
|
||||
Key ed25519.PublicKey
|
||||
RoutingEntries uint64
|
||||
}
|
||||
|
||||
type Peer struct {
|
||||
Key ed25519.PublicKey
|
||||
Root ed25519.PublicKey
|
||||
Coords []uint64
|
||||
Port uint64
|
||||
Remote string
|
||||
type PeerInfo struct {
|
||||
URI string
|
||||
Up bool
|
||||
Inbound bool
|
||||
LastError error
|
||||
LastErrorTime time.Time
|
||||
Key ed25519.PublicKey
|
||||
Root ed25519.PublicKey
|
||||
Coords []uint64
|
||||
Port uint64
|
||||
Priority uint8
|
||||
Cost uint64
|
||||
RXBytes uint64
|
||||
TXBytes uint64
|
||||
RXRate uint64
|
||||
TXRate uint64
|
||||
Uptime time.Duration
|
||||
Latency time.Duration
|
||||
}
|
||||
|
||||
type DHTEntry struct {
|
||||
Key ed25519.PublicKey
|
||||
Port uint64
|
||||
Rest uint64
|
||||
type TreeEntryInfo struct {
|
||||
Key ed25519.PublicKey
|
||||
Parent ed25519.PublicKey
|
||||
Sequence uint64
|
||||
//Port uint64
|
||||
//Rest uint64
|
||||
}
|
||||
|
||||
type PathEntry struct {
|
||||
Key ed25519.PublicKey
|
||||
Path []uint64
|
||||
type PathEntryInfo struct {
|
||||
Key ed25519.PublicKey
|
||||
Path []uint64
|
||||
Sequence uint64
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
Key ed25519.PublicKey
|
||||
type SessionInfo struct {
|
||||
Key ed25519.PublicKey
|
||||
RXBytes uint64
|
||||
TXBytes uint64
|
||||
Uptime time.Duration
|
||||
}
|
||||
|
||||
func (c *Core) GetSelf() Self {
|
||||
var self Self
|
||||
s := c.pc.PacketConn.Debug.GetSelf()
|
||||
func (c *Core) GetSelf() SelfInfo {
|
||||
var self SelfInfo
|
||||
s := c.PacketConn.PacketConn.Debug.GetSelf()
|
||||
self.Key = s.Key
|
||||
self.Root = s.Root
|
||||
self.Coords = s.Coords
|
||||
self.RoutingEntries = s.RoutingEntries
|
||||
return self
|
||||
}
|
||||
|
||||
func (c *Core) GetPeers() []Peer {
|
||||
var peers []Peer
|
||||
names := make(map[net.Conn]string)
|
||||
c.links.mutex.Lock()
|
||||
for _, info := range c.links.links {
|
||||
names[info.conn] = info.lname
|
||||
func (c *Core) GetPeers() []PeerInfo {
|
||||
peers := []PeerInfo{}
|
||||
conns := map[net.Conn]network.DebugPeerInfo{}
|
||||
iwpeers := c.PacketConn.PacketConn.Debug.GetPeers()
|
||||
for _, p := range iwpeers {
|
||||
conns[p.Conn] = p
|
||||
}
|
||||
c.links.mutex.Unlock()
|
||||
ps := c.pc.PacketConn.Debug.GetPeers()
|
||||
for _, p := range ps {
|
||||
var info Peer
|
||||
info.Key = p.Key
|
||||
info.Root = p.Root
|
||||
info.Coords = p.Coords
|
||||
info.Port = p.Port
|
||||
info.Remote = p.Conn.RemoteAddr().String()
|
||||
if name := names[p.Conn]; name != "" {
|
||||
info.Remote = name
|
||||
|
||||
phony.Block(&c.links, func() {
|
||||
for info, state := range c.links._links {
|
||||
var peerinfo PeerInfo
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
peers = append(peers, info)
|
||||
}
|
||||
})
|
||||
|
||||
return peers
|
||||
}
|
||||
|
||||
func (c *Core) GetDHT() []DHTEntry {
|
||||
var dhts []DHTEntry
|
||||
ds := c.pc.PacketConn.Debug.GetDHT()
|
||||
for _, d := range ds {
|
||||
var info DHTEntry
|
||||
info.Key = d.Key
|
||||
info.Port = d.Port
|
||||
info.Rest = d.Rest
|
||||
dhts = append(dhts, info)
|
||||
func (c *Core) GetTree() []TreeEntryInfo {
|
||||
var trees []TreeEntryInfo
|
||||
ts := c.PacketConn.PacketConn.Debug.GetTree()
|
||||
for _, t := range ts {
|
||||
var info TreeEntryInfo
|
||||
info.Key = t.Key
|
||||
info.Parent = t.Parent
|
||||
info.Sequence = t.Sequence
|
||||
//info.Port = d.Port
|
||||
//info.Rest = d.Rest
|
||||
trees = append(trees, info)
|
||||
}
|
||||
return dhts
|
||||
return trees
|
||||
}
|
||||
|
||||
func (c *Core) GetPaths() []PathEntry {
|
||||
var paths []PathEntry
|
||||
ps := c.pc.PacketConn.Debug.GetPaths()
|
||||
func (c *Core) GetPaths() []PathEntryInfo {
|
||||
var paths []PathEntryInfo
|
||||
ps := c.PacketConn.PacketConn.Debug.GetPaths()
|
||||
for _, p := range ps {
|
||||
var info PathEntry
|
||||
var info PathEntryInfo
|
||||
info.Key = p.Key
|
||||
info.Sequence = p.Sequence
|
||||
info.Path = p.Path
|
||||
paths = append(paths, info)
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
func (c *Core) GetSessions() []Session {
|
||||
var sessions []Session
|
||||
ss := c.pc.Debug.GetSessions()
|
||||
func (c *Core) GetSessions() []SessionInfo {
|
||||
var sessions []SessionInfo
|
||||
ss := c.PacketConn.Debug.GetSessions()
|
||||
for _, s := range ss {
|
||||
var info Session
|
||||
var info SessionInfo
|
||||
info.Key = s.Key
|
||||
info.RXBytes = s.RX
|
||||
info.TXBytes = s.TX
|
||||
info.Uptime = s.Uptime
|
||||
sessions = append(sessions, info)
|
||||
}
|
||||
return sessions
|
||||
|
@ -118,8 +153,15 @@ func (c *Core) GetSessions() []Session {
|
|||
// Listen starts a new listener (either TCP or TLS). The input should be a url.URL
|
||||
// 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.
|
||||
func (c *Core) Listen(u *url.URL, sintf string) (*TcpListener, error) {
|
||||
return c.links.tcp.listenURL(u, sintf)
|
||||
func (c *Core) Listen(u *url.URL, sintf string) (*Listener, error) {
|
||||
return c.links.listen(u, sintf, false)
|
||||
}
|
||||
|
||||
// ListenLocal starts a listener, like the Listen function, but is used for
|
||||
// more trustworthy situations where you want to ignore AllowedPublicKeys, i.e.
|
||||
// with multicast listeners.
|
||||
func (c *Core) ListenLocal(u *url.URL, sintf string) (*Listener, error) {
|
||||
return c.links.listen(u, sintf, true)
|
||||
}
|
||||
|
||||
// Address gets the IPv6 address of the Yggdrasil node. This is always a /128
|
||||
|
@ -147,154 +189,76 @@ func (c *Core) Subnet() net.IPNet {
|
|||
// may be useful if you want to redirect the output later. Note that this
|
||||
// expects a Logger from the github.com/gologme/log package and not from Go's
|
||||
// built-in log package.
|
||||
func (c *Core) SetLogger(log *log.Logger) {
|
||||
func (c *Core) SetLogger(log Logger) {
|
||||
c.log = log
|
||||
}
|
||||
|
||||
// AddPeer adds a peer. This should be specified in the peer URI format, e.g.:
|
||||
// tcp://a.b.c.d:e
|
||||
// socks://a.b.c.d:e/f.g.h.i:j
|
||||
//
|
||||
// tcp://a.b.c.d:e
|
||||
// socks://a.b.c.d:e/f.g.h.i:j
|
||||
//
|
||||
// This adds the peer to the peer list, so that they will be called again if the
|
||||
// connection drops.
|
||||
/*
|
||||
func (c *Core) AddPeer(addr string, sintf string) error {
|
||||
if err := c.CallPeer(addr, sintf); err != nil {
|
||||
// TODO: We maybe want this to write the peer to the persistent
|
||||
// configuration even if a connection attempt fails, but first we'll need to
|
||||
// move the code to check the peer URI so that we don't deliberately save a
|
||||
// peer with a known bad URI. Loading peers from config should really do the
|
||||
// same thing too but I don't think that happens today
|
||||
return err
|
||||
}
|
||||
c.config.Mutex.Lock()
|
||||
defer c.config.Mutex.Unlock()
|
||||
if sintf == "" {
|
||||
for _, peer := range c.config.Current.Peers {
|
||||
if peer == addr {
|
||||
return errors.New("peer already added")
|
||||
}
|
||||
}
|
||||
c.config.Current.Peers = append(c.config.Current.Peers, addr)
|
||||
} else {
|
||||
if _, ok := c.config.Current.InterfacePeers[sintf]; ok {
|
||||
for _, peer := range c.config.Current.InterfacePeers[sintf] {
|
||||
if peer == addr {
|
||||
return errors.New("peer already added")
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, ok := c.config.Current.InterfacePeers[sintf]; !ok {
|
||||
c.config.Current.InterfacePeers[sintf] = []string{addr}
|
||||
} else {
|
||||
c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf], addr)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
func (c *Core) AddPeer(u *url.URL, sintf string) error {
|
||||
return c.links.add(u, sintf, linkTypePersistent)
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
func (c *Core) RemovePeer(addr string, sintf string) error {
|
||||
if sintf == "" {
|
||||
for i, peer := range c.config.Current.Peers {
|
||||
if peer == addr {
|
||||
c.config.Current.Peers = append(c.config.Current.Peers[:i], c.config.Current.Peers[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if _, ok := c.config.Current.InterfacePeers[sintf]; ok {
|
||||
for i, peer := range c.config.Current.InterfacePeers[sintf] {
|
||||
if peer == addr {
|
||||
c.config.Current.InterfacePeers[sintf] = append(c.config.Current.InterfacePeers[sintf][:i], c.config.Current.InterfacePeers[sintf][i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic("TODO") // Get the net.Conn to this peer (if any) and close it
|
||||
c.peers.Act(nil, func() {
|
||||
ports := c.peers.ports
|
||||
for _, peer := range ports {
|
||||
if addr == peer.intf.name() {
|
||||
c.peers._removePeer(peer)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
// RemovePeer removes a peer. The peer should be specified in URI format, see AddPeer.
|
||||
// The peer is not disconnected immediately.
|
||||
func (c *Core) RemovePeer(u *url.URL, sintf string) error {
|
||||
return c.links.remove(u, sintf, linkTypePersistent)
|
||||
}
|
||||
*/
|
||||
|
||||
// CallPeer calls a peer once. This should be specified in the peer URI format,
|
||||
// e.g.:
|
||||
// tcp://a.b.c.d:e
|
||||
// socks://a.b.c.d:e/f.g.h.i:j
|
||||
//
|
||||
// tcp://a.b.c.d:e
|
||||
// socks://a.b.c.d:e/f.g.h.i:j
|
||||
//
|
||||
// This does not add the peer to the peer list, so if the connection drops, the
|
||||
// peer will not be called again automatically.
|
||||
func (c *Core) CallPeer(u *url.URL, sintf string) error {
|
||||
return c.links.call(u, sintf)
|
||||
return c.links.add(u, sintf, linkTypeEphemeral)
|
||||
}
|
||||
|
||||
func (c *Core) PublicKey() ed25519.PublicKey {
|
||||
return c.public
|
||||
}
|
||||
|
||||
func (c *Core) MaxMTU() uint64 {
|
||||
return c.store.maxSessionMTU()
|
||||
}
|
||||
|
||||
func (c *Core) SetMTU(mtu uint64) {
|
||||
if mtu < 1280 {
|
||||
mtu = 1280
|
||||
}
|
||||
c.store.mutex.Lock()
|
||||
c.store.mtu = mtu
|
||||
c.store.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (c *Core) MTU() uint64 {
|
||||
c.store.mutex.Lock()
|
||||
mtu := c.store.mtu
|
||||
c.store.mutex.Unlock()
|
||||
return mtu
|
||||
}
|
||||
|
||||
// Implement io.ReadWriteCloser
|
||||
|
||||
func (c *Core) Read(p []byte) (n int, err error) {
|
||||
n, err = c.store.readPC(p)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Core) Write(p []byte) (n int, err error) {
|
||||
n, err = c.store.writePC(p)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Core) Close() error {
|
||||
c.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hack to get the admin stuff working, TODO something cleaner
|
||||
|
||||
type AddHandler interface {
|
||||
AddHandler(name string, args []string, handlerfunc func(json.RawMessage) (interface{}, error)) error
|
||||
AddHandler(name, desc string, args []string, handlerfunc AddHandlerFunc) error
|
||||
}
|
||||
|
||||
type AddHandlerFunc func(json.RawMessage) (interface{}, error)
|
||||
|
||||
// SetAdmin must be called after Init and before Start.
|
||||
// It sets the admin handler for NodeInfo and the Debug admin functions.
|
||||
func (c *Core) SetAdmin(a AddHandler) error {
|
||||
if err := a.AddHandler("getNodeInfo", []string{"key"}, c.proto.nodeinfo.nodeInfoAdminHandler); err != nil {
|
||||
if err := a.AddHandler(
|
||||
"getNodeInfo", "Request nodeinfo from a remote node by its public key", []string{"key"},
|
||||
c.proto.nodeinfo.nodeInfoAdminHandler,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.AddHandler("debug_remoteGetSelf", []string{"key"}, c.proto.getSelfHandler); err != nil {
|
||||
if err := a.AddHandler(
|
||||
"debug_remoteGetSelf", "Debug use only", []string{"key"},
|
||||
c.proto.getSelfHandler,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.AddHandler("debug_remoteGetPeers", []string{"key"}, c.proto.getPeersHandler); err != nil {
|
||||
if err := a.AddHandler(
|
||||
"debug_remoteGetPeers", "Debug use only", []string{"key"},
|
||||
c.proto.getPeersHandler,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.AddHandler("debug_remoteGetDHT", []string{"key"}, c.proto.getDHTHandler); err != nil {
|
||||
if err := a.AddHandler(
|
||||
"debug_remoteGetTree", "Debug use only", []string{"key"},
|
||||
c.proto.getTreeHandler,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
321
src/core/core.go
321
src/core/core.go
|
@ -3,19 +3,20 @@ package core
|
|||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
iw "github.com/Arceliar/ironwood/encrypted"
|
||||
iwe "github.com/Arceliar/ironwood/encrypted"
|
||||
iwn "github.com/Arceliar/ironwood/network"
|
||||
iwt "github.com/Arceliar/ironwood/types"
|
||||
"github.com/Arceliar/phony"
|
||||
"github.com/gologme/log"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
//"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/version"
|
||||
)
|
||||
|
||||
|
@ -26,113 +27,36 @@ type Core struct {
|
|||
// We're going to keep our own copy of the provided config - that way we can
|
||||
// guarantee that it will be covered by the mutex
|
||||
phony.Inbox
|
||||
pc *iw.PacketConn
|
||||
config *config.NodeConfig // Config
|
||||
*iwe.PacketConn
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
secret ed25519.PrivateKey
|
||||
public ed25519.PublicKey
|
||||
links links
|
||||
proto protoHandler
|
||||
store keyStore
|
||||
log *log.Logger
|
||||
log Logger
|
||||
addPeerTimer *time.Timer
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
config struct {
|
||||
tls *tls.Config // immutable after startup
|
||||
//_peers map[Peer]*linkInfo // configurable after startup
|
||||
_listeners map[ListenAddress]struct{} // configurable after startup
|
||||
peerFilter func(ip net.IP) bool // immutable after startup
|
||||
nodeinfo NodeInfo // immutable after startup
|
||||
nodeinfoPrivacy NodeInfoPrivacy // immutable after startup
|
||||
_allowedPublicKeys map[[32]byte]struct{} // configurable after startup
|
||||
}
|
||||
pathNotify func(ed25519.PublicKey)
|
||||
}
|
||||
|
||||
func (c *Core) _init() error {
|
||||
// TODO separate init and start functions
|
||||
// Init sets up structs
|
||||
// Start launches goroutines that depend on structs being set up
|
||||
// This is pretty much required to completely avoid race conditions
|
||||
c.config.RLock()
|
||||
defer c.config.RUnlock()
|
||||
func New(cert *tls.Certificate, logger Logger, opts ...SetupOption) (*Core, error) {
|
||||
c := &Core{
|
||||
log: logger,
|
||||
}
|
||||
c.ctx, c.cancel = context.WithCancel(context.Background())
|
||||
if c.log == nil {
|
||||
c.log = log.New(ioutil.Discard, "", 0)
|
||||
c.log = log.New(io.Discard, "", 0)
|
||||
}
|
||||
|
||||
sigPriv, err := hex.DecodeString(c.config.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(sigPriv) < ed25519.PrivateKeySize {
|
||||
return errors.New("PrivateKey is incorrect length")
|
||||
}
|
||||
|
||||
c.secret = ed25519.PrivateKey(sigPriv)
|
||||
c.public = c.secret.Public().(ed25519.PublicKey)
|
||||
// TODO check public against current.PublicKey, error if they don't match
|
||||
|
||||
c.pc, err = iw.NewPacketConn(c.secret)
|
||||
c.ctx, c.ctxCancel = context.WithCancel(context.Background())
|
||||
c.store.init(c)
|
||||
c.proto.init(c)
|
||||
if err := c.proto.nodeinfo.setNodeInfo(c.config.NodeInfo, c.config.NodeInfoPrivacy); err != nil {
|
||||
return fmt.Errorf("setNodeInfo: %w", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 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() {
|
||||
c.config.RLock()
|
||||
defer c.config.RUnlock()
|
||||
|
||||
if c.addPeerTimer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 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, "") // TODO: this should be acted and not in a goroutine?
|
||||
}
|
||||
|
||||
// Add peers from the InterfacePeers section
|
||||
for intf, intfpeers := range c.config.InterfacePeers {
|
||||
for _, peer := range intfpeers {
|
||||
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, intf) // TODO: this should be acted and not in a goroutine?
|
||||
}
|
||||
}
|
||||
|
||||
c.addPeerTimer = time.AfterFunc(time.Minute, func() {
|
||||
c.Act(nil, c._addPeerLoop)
|
||||
})
|
||||
}
|
||||
|
||||
// Start starts up Yggdrasil using the provided config.NodeConfig, and outputs
|
||||
// debug logging through the provided log.Logger. The started stack will include
|
||||
// TCP and UDP sockets, a multicast discovery socket, an admin socket, router,
|
||||
// switch and DHT node. A config.NodeState is returned which contains both the
|
||||
// current and previous configurations (from reconfigures).
|
||||
func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) (err error) {
|
||||
phony.Block(c, func() {
|
||||
err = c._start(nc, log)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// This function is unsafe and should only be ran by the core actor.
|
||||
func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) error {
|
||||
c.log = log
|
||||
c.config = nc
|
||||
|
||||
if name := version.BuildName(); name != "unknown" {
|
||||
c.log.Infoln("Build name:", name)
|
||||
}
|
||||
|
@ -140,44 +64,189 @@ func (c *Core) _start(nc *config.NodeConfig, log *log.Logger) error {
|
|||
c.log.Infoln("Build version:", version)
|
||||
}
|
||||
|
||||
c.log.Infoln("Starting up...")
|
||||
if err := c._init(); err != nil {
|
||||
c.log.Errorln("Failed to initialize core")
|
||||
return err
|
||||
var err error
|
||||
c.config._listeners = map[ListenAddress]struct{}{}
|
||||
c.config._allowedPublicKeys = map[[32]byte]struct{}{}
|
||||
for _, opt := range opts {
|
||||
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 cert == nil || cert.PrivateKey == nil {
|
||||
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)
|
||||
if err := c.links.init(c); err != nil {
|
||||
c.log.Errorln("Failed to start link interfaces")
|
||||
return 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 {
|
||||
return nil, fmt.Errorf("error setting node info: %w", err)
|
||||
}
|
||||
for listenaddr := range c.config._listeners {
|
||||
u, err := url.Parse(string(listenaddr))
|
||||
if err != nil {
|
||||
c.log.Errorf("Invalid listener URI %q specified, ignoring\n", listenaddr)
|
||||
continue
|
||||
}
|
||||
if _, err = c.links.listen(u, "", false); err != nil {
|
||||
c.log.Errorf("Failed to start listener %q: %s\n", listenaddr, err)
|
||||
}
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
c.addPeerTimer = time.AfterFunc(0, func() {
|
||||
c.Act(nil, c._addPeerLoop)
|
||||
func (c *Core) RetryPeersNow() {
|
||||
phony.Block(&c.links, func() {
|
||||
for _, l := range c.links._links {
|
||||
select {
|
||||
case l.kick <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
c.log.Infoln("Startup complete")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop shuts down the Yggdrasil node.
|
||||
func (c *Core) Stop() {
|
||||
phony.Block(c, c._stop)
|
||||
phony.Block(c, func() {
|
||||
c.log.Infoln("Stopping...")
|
||||
_ = c._close()
|
||||
c.log.Infoln("Stopped")
|
||||
})
|
||||
}
|
||||
|
||||
// This function is unsafe and should only be ran by the core actor.
|
||||
func (c *Core) _stop() {
|
||||
c.log.Infoln("Stopping...")
|
||||
c.ctxCancel()
|
||||
c.pc.Close()
|
||||
func (c *Core) _close() error {
|
||||
c.cancel()
|
||||
c.links.shutdown()
|
||||
err := c.PacketConn.Close()
|
||||
if c.addPeerTimer != nil {
|
||||
c.addPeerTimer.Stop()
|
||||
c.addPeerTimer = nil
|
||||
}
|
||||
_ = c.links.stop()
|
||||
/* FIXME this deadlocks, need a waitgroup or something to coordinate shutdown
|
||||
for _, peer := range c.GetPeers() {
|
||||
c.DisconnectPeer(peer.Port)
|
||||
}
|
||||
*/
|
||||
c.log.Infoln("Stopped")
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Core) MTU() uint64 {
|
||||
const sessionTypeOverhead = 1
|
||||
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) {
|
||||
buf := allocBytes(int(c.PacketConn.MTU()))
|
||||
defer freeBytes(buf)
|
||||
for {
|
||||
bs := buf
|
||||
n, from, err = c.PacketConn.ReadFrom(bs)
|
||||
if err != nil {
|
||||
return 0, from, err
|
||||
}
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
switch bs[0] {
|
||||
case typeSessionTraffic:
|
||||
// This is what we want to handle here
|
||||
case typeSessionProto:
|
||||
var key keyArray
|
||||
copy(key[:], from.(iwt.Addr))
|
||||
data := append([]byte(nil), bs[1:n]...)
|
||||
c.proto.handleProto(nil, key, data)
|
||||
continue
|
||||
default:
|
||||
continue
|
||||
}
|
||||
bs = bs[1:n]
|
||||
copy(p, bs)
|
||||
if len(p) < len(bs) {
|
||||
n = len(p)
|
||||
} else {
|
||||
n = len(bs)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
buf := allocBytes(0)
|
||||
defer func() { freeBytes(buf) }()
|
||||
buf = append(buf, typeSessionTraffic)
|
||||
buf = append(buf, p...)
|
||||
n, err = c.PacketConn.WriteTo(buf, addr)
|
||||
if n > 0 {
|
||||
n -= 1
|
||||
}
|
||||
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 {
|
||||
Printf(string, ...interface{})
|
||||
Println(...interface{})
|
||||
Infof(string, ...interface{})
|
||||
Infoln(...interface{})
|
||||
Warnf(string, ...interface{})
|
||||
Warnln(...interface{})
|
||||
Errorf(string, ...interface{})
|
||||
Errorln(...interface{})
|
||||
Debugf(string, ...interface{})
|
||||
Debugln(...interface{})
|
||||
Traceln(...interface{})
|
||||
}
|
||||
|
|
|
@ -2,28 +2,16 @@ package core
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"math/rand"
|
||||
"crypto/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gologme/log"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
|
||||
)
|
||||
|
||||
// GenerateConfig produces default configuration with suitable modifications for tests.
|
||||
func GenerateConfig() *config.NodeConfig {
|
||||
cfg := defaults.GenerateConfig()
|
||||
cfg.AdminListen = "none"
|
||||
cfg.Listen = []string{"tcp://127.0.0.1:0"}
|
||||
cfg.IfName = "none"
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
// GetLoggerWithPrefix creates a new logger instance with prefix.
|
||||
// If verbose is set to true, three log levels are enabled: "info", "warn", "error".
|
||||
func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger {
|
||||
|
@ -37,29 +25,65 @@ func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger {
|
|||
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.
|
||||
// Verbosity flag is passed to logger.
|
||||
func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) {
|
||||
nodeA = new(Core)
|
||||
if err := nodeA.Start(GenerateConfig(), GetLoggerWithPrefix("A: ", verbose)); err != nil {
|
||||
var err error
|
||||
|
||||
cfgA, cfgB := config.GenerateConfig(), config.GenerateConfig()
|
||||
if err = cfgA.GenerateSelfSignedCertificate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
nodeA.SetMTU(1500)
|
||||
|
||||
nodeB = new(Core)
|
||||
if err := nodeB.Start(GenerateConfig(), GetLoggerWithPrefix("B: ", verbose)); err != nil {
|
||||
if err = cfgB.GenerateSelfSignedCertificate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
nodeB.SetMTU(1500)
|
||||
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = nodeB.CallPeer(u, "")
|
||||
nodeAListener, err := nodeA.Listen(nodeAListenURL, "")
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
|
@ -78,7 +102,13 @@ func WaitConnected(nodeA, nodeB *Core) bool {
|
|||
// It may take up to 3 seconds, but let's wait 5.
|
||||
for i := 0; i < 50; i++ {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +124,7 @@ func CreateEchoListener(t testing.TB, nodeA *Core, bufLen int, repeats int) chan
|
|||
buf := make([]byte, bufLen)
|
||||
res := make([]byte, bufLen)
|
||||
for i := 0; i < repeats; i++ {
|
||||
n, err := nodeA.Read(buf)
|
||||
n, from, err := nodeA.ReadFrom(buf)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -106,7 +136,7 @@ func CreateEchoListener(t testing.TB, nodeA *Core, bufLen int, repeats int) chan
|
|||
copy(res, buf)
|
||||
copy(res[8:24], buf[24:40])
|
||||
copy(res[24:40], buf[8:24])
|
||||
_, err = nodeA.Write(res)
|
||||
_, err = nodeA.WriteTo(res, from)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -137,16 +167,16 @@ func TestCore_Start_Transfer(t *testing.T) {
|
|||
|
||||
// Send
|
||||
msg := make([]byte, msgLen)
|
||||
rand.Read(msg[40:])
|
||||
_, _ = rand.Read(msg[40:])
|
||||
msg[0] = 0x60
|
||||
copy(msg[8:24], nodeB.Address())
|
||||
copy(msg[24:40], nodeA.Address())
|
||||
_, err := nodeB.Write(msg)
|
||||
_, err := nodeB.WriteTo(msg, nodeA.LocalAddr())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buf := make([]byte, msgLen)
|
||||
_, err = nodeB.Read(buf)
|
||||
_, _, err = nodeB.ReadFrom(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -169,7 +199,7 @@ func BenchmarkCore_Start_Transfer(b *testing.B) {
|
|||
|
||||
// Send
|
||||
msg := make([]byte, msgLen)
|
||||
rand.Read(msg[40:])
|
||||
_, _ = rand.Read(msg[40:])
|
||||
msg[0] = 0x60
|
||||
copy(msg[8:24], nodeB.Address())
|
||||
copy(msg[24:40], nodeA.Address())
|
||||
|
@ -179,15 +209,82 @@ func BenchmarkCore_Start_Transfer(b *testing.B) {
|
|||
b.SetBytes(int64(msgLen))
|
||||
b.ResetTimer()
|
||||
|
||||
addr := nodeA.LocalAddr()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := nodeB.Write(msg)
|
||||
_, err := nodeB.WriteTo(msg, addr)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_, err = nodeB.Read(buf)
|
||||
_, _, err = nodeB.ReadFrom(buf)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
<-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,33 +1,19 @@
|
|||
// +build debug
|
||||
|
||||
package core
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
)
|
||||
|
||||
import _ "net/http/pprof"
|
||||
import "net/http"
|
||||
import "runtime"
|
||||
import "os"
|
||||
|
||||
import "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() {
|
||||
envVarName := "PPROFLISTEN"
|
||||
hostPort := os.Getenv(envVarName)
|
||||
switch {
|
||||
case hostPort == "":
|
||||
fmt.Fprintf(os.Stderr, "DEBUG: %s not set, profiler not started.\n", envVarName)
|
||||
default:
|
||||
if hostPort := os.Getenv(envVarName); 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
|
||||
}
|
||||
|
|
866
src/core/link.go
866
src/core/link.go
|
@ -1,251 +1,767 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
//"sync/atomic"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/Arceliar/phony"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/util"
|
||||
"golang.org/x/net/proxy"
|
||||
//"github.com/Arceliar/phony" // TODO? use instead of mutexes
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
type linkType int
|
||||
|
||||
const (
|
||||
linkTypePersistent linkType = iota // Statically configured
|
||||
linkTypeEphemeral // Multicast discovered
|
||||
linkTypeIncoming // Incoming connection
|
||||
)
|
||||
|
||||
const defaultBackoffLimit = time.Second << 12 // 1h8m16s
|
||||
const minimumBackoffLimit = time.Second * 30
|
||||
|
||||
type links struct {
|
||||
core *Core
|
||||
mutex sync.RWMutex // protects links below
|
||||
links map[linkInfo]*link
|
||||
tcp tcp // TCP interface support
|
||||
stopped chan struct{}
|
||||
// TODO timeout (to remove from switch), read from config.ReadTimeout
|
||||
phony.Inbox
|
||||
core *Core
|
||||
tcp *linkTCP // TCP interface support
|
||||
tls *linkTLS // TLS interface support
|
||||
unix *linkUNIX // UNIX interface support
|
||||
socks *linkSOCKS // SOCKS interface support
|
||||
quic *linkQUIC // QUIC interface support
|
||||
ws *linkWS // WS interface support
|
||||
wss *linkWSS // WSS interface support
|
||||
// _links can only be modified safely from within the links actor
|
||||
_links map[linkInfo]*link // *link is nil if connection in progress
|
||||
_listeners map[*Listener]context.CancelFunc
|
||||
}
|
||||
|
||||
type linkProtocol interface {
|
||||
dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error)
|
||||
listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error)
|
||||
}
|
||||
|
||||
// linkInfo is used as a map key
|
||||
type linkInfo struct {
|
||||
key keyArray
|
||||
linkType string // Type of link, e.g. TCP, AWDL
|
||||
local string // Local name or address
|
||||
remote string // Remote name or address
|
||||
uri string // Peering URI in complete form
|
||||
sintf string // Peering source interface (i.e. from InterfacePeers)
|
||||
}
|
||||
|
||||
// link tracks the state of a connection, either persistent or non-persistent
|
||||
type link struct {
|
||||
lname string
|
||||
links *links
|
||||
conn net.Conn
|
||||
options linkOptions
|
||||
info linkInfo
|
||||
incoming bool
|
||||
force bool
|
||||
closed chan struct{}
|
||||
ctx context.Context // Connection context
|
||||
cancel context.CancelFunc // Stop future redial attempts (when peer removed)
|
||||
kick chan struct{} // Attempt to reconnect now, if backing off
|
||||
linkType linkType // Type of link, i.e. outbound/inbound, persistent/ephemeral
|
||||
linkProto string // Protocol carrier of link, e.g. TCP, AWDL
|
||||
// The remaining fields can only be modified safely from within the links actor
|
||||
_conn *linkConn // Connected link, if any, nil if not connected
|
||||
_err error // Last error on the connection, if any
|
||||
_errtime time.Time // Last time an error occurred
|
||||
}
|
||||
|
||||
type linkOptions struct {
|
||||
pinnedEd25519Keys map[keyArray]struct{}
|
||||
priority uint8
|
||||
tlsSNI string
|
||||
password []byte
|
||||
maxBackoff time.Duration
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
listener net.Listener
|
||||
ctx context.Context
|
||||
Cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (l *Listener) Addr() net.Addr {
|
||||
return l.listener.Addr()
|
||||
}
|
||||
|
||||
func (l *links) init(c *Core) error {
|
||||
l.core = c
|
||||
l.mutex.Lock()
|
||||
l.links = make(map[linkInfo]*link)
|
||||
l.mutex.Unlock()
|
||||
l.stopped = make(chan struct{})
|
||||
|
||||
if err := l.tcp.init(l); err != nil {
|
||||
c.log.Errorln("Failed to start TCP interface")
|
||||
return err
|
||||
}
|
||||
l.tcp = l.newLinkTCP()
|
||||
l.tls = l.newLinkTLS(l.tcp)
|
||||
l.unix = l.newLinkUNIX()
|
||||
l.socks = l.newLinkSOCKS()
|
||||
l.quic = l.newLinkQUIC()
|
||||
l.ws = l.newLinkWS()
|
||||
l.wss = l.newLinkWSS()
|
||||
l._links = make(map[linkInfo]*link)
|
||||
l._listeners = make(map[*Listener]context.CancelFunc)
|
||||
|
||||
l.Act(nil, l._updateAverages)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *links) call(u *url.URL, sintf string) error {
|
||||
//u, err := url.Parse(uri)
|
||||
//if err != nil {
|
||||
// return fmt.Errorf("peer %s is not correctly formatted (%s)", uri, err)
|
||||
//}
|
||||
tcpOpts := tcpOptions{}
|
||||
if pubkeys, ok := u.Query()["key"]; ok && len(pubkeys) > 0 {
|
||||
tcpOpts.pinnedEd25519Keys = make(map[keyArray]struct{})
|
||||
for _, pubkey := range pubkeys {
|
||||
if sigPub, err := hex.DecodeString(pubkey); err == nil {
|
||||
var sigPubKey keyArray
|
||||
copy(sigPubKey[:], sigPub)
|
||||
tcpOpts.pinnedEd25519Keys[sigPubKey] = struct{}{}
|
||||
func (l *links) _updateAverages() {
|
||||
select {
|
||||
case <-l.core.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
for _, l := range l._links {
|
||||
if l._conn == nil {
|
||||
continue
|
||||
}
|
||||
rx := atomic.LoadUint64(&l._conn.rx)
|
||||
tx := atomic.LoadUint64(&l._conn.tx)
|
||||
lastrx := atomic.LoadUint64(&l._conn.lastrx)
|
||||
lasttx := atomic.LoadUint64(&l._conn.lasttx)
|
||||
atomic.StoreUint64(&l._conn.rxrate, rx-lastrx)
|
||||
atomic.StoreUint64(&l._conn.txrate, tx-lasttx)
|
||||
atomic.StoreUint64(&l._conn.lastrx, rx)
|
||||
atomic.StoreUint64(&l._conn.lasttx, tx)
|
||||
}
|
||||
|
||||
time.AfterFunc(time.Second, func() {
|
||||
l.Act(nil, l._updateAverages)
|
||||
})
|
||||
}
|
||||
|
||||
func (l *links) shutdown() {
|
||||
phony.Block(l, func() {
|
||||
for _, cancel := range l._listeners {
|
||||
cancel()
|
||||
}
|
||||
for _, link := range l._links {
|
||||
if link._conn != nil {
|
||||
_ = link._conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
switch u.Scheme {
|
||||
})
|
||||
}
|
||||
|
||||
type linkError string
|
||||
|
||||
func (e linkError) Error() string { return string(e) }
|
||||
|
||||
const ErrLinkAlreadyConfigured = linkError("peer is already configured")
|
||||
const ErrLinkNotConfigured = linkError("peer is not configured")
|
||||
const ErrLinkPriorityInvalid = linkError("priority value is invalid")
|
||||
const ErrLinkPinnedKeyInvalid = linkError("pinned public key is invalid")
|
||||
const ErrLinkPasswordInvalid = linkError("invalid password supplied")
|
||||
const ErrLinkUnrecognisedSchema = linkError("link schema unknown")
|
||||
const ErrLinkMaxBackoffInvalid = linkError("max backoff duration invalid")
|
||||
const ErrLinkSNINotSupported = linkError("SNI not supported on this link type")
|
||||
const ErrLinkNoSuitableIPs = linkError("peer has no suitable addresses")
|
||||
|
||||
func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
|
||||
var retErr error
|
||||
phony.Block(l, func() {
|
||||
// Generate the link info and see whether we think we already
|
||||
// have an open peering to this peer.
|
||||
lu := urlForLinkInfo(*u)
|
||||
info := linkInfo{
|
||||
uri: lu.String(),
|
||||
sintf: sintf,
|
||||
}
|
||||
|
||||
// Collect together the link options, these are global options
|
||||
// that are not specific to any given protocol.
|
||||
options := linkOptions{
|
||||
maxBackoff: defaultBackoffLimit,
|
||||
}
|
||||
for _, pubkey := range u.Query()["key"] {
|
||||
sigPub, err := hex.DecodeString(pubkey)
|
||||
if err != nil {
|
||||
retErr = ErrLinkPinnedKeyInvalid
|
||||
return
|
||||
}
|
||||
var sigPubKey keyArray
|
||||
copy(sigPubKey[:], sigPub)
|
||||
if options.pinnedEd25519Keys == nil {
|
||||
options.pinnedEd25519Keys = map[keyArray]struct{}{}
|
||||
}
|
||||
options.pinnedEd25519Keys[sigPubKey] = struct{}{}
|
||||
}
|
||||
if p := u.Query().Get("priority"); p != "" {
|
||||
pi, err := strconv.ParseUint(p, 10, 8)
|
||||
if err != nil {
|
||||
retErr = ErrLinkPriorityInvalid
|
||||
return
|
||||
}
|
||||
options.priority = uint8(pi)
|
||||
}
|
||||
if p := u.Query().Get("password"); p != "" {
|
||||
if len(p) > blake2b.Size {
|
||||
retErr = ErrLinkPasswordInvalid
|
||||
return
|
||||
}
|
||||
options.password = []byte(p)
|
||||
}
|
||||
if p := u.Query().Get("maxbackoff"); p != "" {
|
||||
d, err := time.ParseDuration(p)
|
||||
if err != nil || d < minimumBackoffLimit {
|
||||
retErr = ErrLinkMaxBackoffInvalid
|
||||
return
|
||||
}
|
||||
options.maxBackoff = d
|
||||
}
|
||||
// SNI headers must contain hostnames and not IP addresses, so we must make sure
|
||||
// that we do not populate the SNI with an IP literal. We do this by splitting
|
||||
// the host-port combo from the query option and then seeing if it parses to an
|
||||
// IP address successfully or not.
|
||||
if sni := u.Query().Get("sni"); sni != "" {
|
||||
if net.ParseIP(sni) == nil {
|
||||
options.tlsSNI = sni
|
||||
}
|
||||
}
|
||||
// If the SNI is not configured still because the above failed then we'll try
|
||||
// again but this time we'll use the host part of the peering URI instead.
|
||||
if options.tlsSNI == "" {
|
||||
if host, _, err := net.SplitHostPort(u.Host); err == nil && net.ParseIP(host) == nil {
|
||||
options.tlsSNI = host
|
||||
}
|
||||
}
|
||||
|
||||
// If we think we're already connected to this peer, load up
|
||||
// the existing peer state. Try to kick the peer if possible,
|
||||
// which will cause an immediate connection attempt if it is
|
||||
// backing off for some reason.
|
||||
state, ok := l._links[info]
|
||||
if ok && state != nil {
|
||||
select {
|
||||
case state.kick <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
retErr = ErrLinkAlreadyConfigured
|
||||
return
|
||||
}
|
||||
|
||||
// Create the link entry. This will contain the connection
|
||||
// in progress (if any), any error details and a context that
|
||||
// lets the link be cancelled later.
|
||||
state = &link{
|
||||
linkType: linkType,
|
||||
linkProto: strings.ToUpper(u.Scheme),
|
||||
kick: make(chan struct{}),
|
||||
}
|
||||
state.ctx, state.cancel = context.WithCancel(l.core.ctx)
|
||||
|
||||
// Store the state of the link so that it can be queried later.
|
||||
l._links[info] = state
|
||||
|
||||
// Track how many consecutive connection failures we have had,
|
||||
// as we will back off exponentially rather than hammering the
|
||||
// remote node endlessly.
|
||||
var backoff int
|
||||
|
||||
// backoffNow is called when there's a connection error. It
|
||||
// will wait for the specified amount of time and then return
|
||||
// true, unless the peering context was cancelled (due to a
|
||||
// peer removal most likely), in which case it returns false.
|
||||
// The caller should check the return value to decide whether
|
||||
// or not to give up trying.
|
||||
backoffNow := func() bool {
|
||||
if backoff < 32 {
|
||||
backoff++
|
||||
}
|
||||
duration := time.Second << backoff
|
||||
if duration > options.maxBackoff {
|
||||
duration = options.maxBackoff
|
||||
}
|
||||
select {
|
||||
case <-state.kick:
|
||||
return true
|
||||
case <-state.ctx.Done():
|
||||
return false
|
||||
case <-l.core.ctx.Done():
|
||||
return false
|
||||
case <-time.After(duration):
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// resetBackoff is called by the connection handler when the
|
||||
// handshake has successfully completed.
|
||||
resetBackoff := func() {
|
||||
backoff = 0
|
||||
}
|
||||
|
||||
// The goroutine is responsible for attempting the connection
|
||||
// and then running the handler. If the connection is persistent
|
||||
// then the loop will run endlessly, using backoffs as needed.
|
||||
// Otherwise the loop will end, cleaning up the link entry.
|
||||
go func() {
|
||||
defer phony.Block(l, func() {
|
||||
if l._links[info] == state {
|
||||
delete(l._links, info)
|
||||
}
|
||||
})
|
||||
|
||||
// This loop will run each and every time we want to attempt
|
||||
// a connection to this peer.
|
||||
// TODO get rid of this loop, this is *exactly* what time.AfterFunc is for, we should just send a signal to the links actor to kick off a goroutine as needed
|
||||
for {
|
||||
select {
|
||||
case <-state.ctx.Done():
|
||||
// The peering context has been cancelled, so don't try
|
||||
// to dial again.
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
conn, err := l.connect(state.ctx, u, info, options)
|
||||
if err != nil || conn == nil {
|
||||
if err == nil && conn == nil {
|
||||
l.core.log.Warnf("Link %q reached inconsistent error state", u.String())
|
||||
}
|
||||
if linkType == linkTypePersistent {
|
||||
// If the link is a persistent configured peering,
|
||||
// store information about the connection error so
|
||||
// that we can report it through the admin socket.
|
||||
phony.Block(l, func() {
|
||||
state._conn = nil
|
||||
state._err = err
|
||||
state._errtime = time.Now()
|
||||
})
|
||||
|
||||
// Back off for a bit. If true is returned here, we
|
||||
// can continue onto the next loop iteration to try
|
||||
// the next connection.
|
||||
if backoffNow() {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
// Ephemeral and incoming connections don't remain
|
||||
// after a connection failure, so exit out of the
|
||||
// loop and clean up the link entry.
|
||||
break
|
||||
}
|
||||
|
||||
// The linkConn wrapper allows us to track the number of
|
||||
// bytes written to and read from this connection without
|
||||
// the help of ironwood.
|
||||
lc := &linkConn{
|
||||
Conn: conn,
|
||||
up: time.Now(),
|
||||
}
|
||||
|
||||
// Update the link state with our newly wrapped connection.
|
||||
// Clear the error state.
|
||||
var doRet bool
|
||||
phony.Block(l, func() {
|
||||
if state._conn != nil {
|
||||
// If a peering has come up in this time, abort this one.
|
||||
doRet = true
|
||||
}
|
||||
state._conn = lc
|
||||
})
|
||||
if doRet {
|
||||
return
|
||||
}
|
||||
|
||||
// Give the connection to the handler. The handler will block
|
||||
// for the lifetime of the connection.
|
||||
switch err = l.handler(linkType, options, lc, resetBackoff, false); {
|
||||
case err == nil:
|
||||
case errors.Is(err, io.EOF):
|
||||
case errors.Is(err, net.ErrClosed):
|
||||
default:
|
||||
l.core.log.Debugf("Link %s error: %s\n", u.Host, err)
|
||||
}
|
||||
|
||||
// The handler has stopped running so the connection is dead,
|
||||
// try to close the underlying socket just in case and then
|
||||
// update the link state.
|
||||
_ = lc.Close()
|
||||
phony.Block(l, func() {
|
||||
state._conn = nil
|
||||
if err == nil {
|
||||
err = fmt.Errorf("remote side closed the connection")
|
||||
}
|
||||
state._err = err
|
||||
state._errtime = time.Now()
|
||||
})
|
||||
|
||||
// If the link is persistently configured, back off if needed
|
||||
// and then try reconnecting. Otherwise, exit out.
|
||||
if linkType == linkTypePersistent {
|
||||
if backoffNow() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Ephemeral or incoming connections don't reconnect.
|
||||
return
|
||||
}
|
||||
}()
|
||||
})
|
||||
return retErr
|
||||
}
|
||||
|
||||
func (l *links) remove(u *url.URL, sintf string, _ linkType) error {
|
||||
var retErr error
|
||||
phony.Block(l, func() {
|
||||
// Generate the link info and see whether we think we already
|
||||
// have an open peering to this peer.
|
||||
lu := urlForLinkInfo(*u)
|
||||
info := linkInfo{
|
||||
uri: lu.String(),
|
||||
sintf: sintf,
|
||||
}
|
||||
|
||||
// If this peer is already configured then we will close the
|
||||
// connection and stop it from retrying.
|
||||
state, ok := l._links[info]
|
||||
if ok && state != nil {
|
||||
state.cancel()
|
||||
if conn := state._conn; conn != nil {
|
||||
retErr = conn.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
retErr = ErrLinkNotConfigured
|
||||
})
|
||||
return retErr
|
||||
}
|
||||
|
||||
func (l *links) listen(u *url.URL, sintf string, local bool) (*Listener, error) {
|
||||
ctx, ctxcancel := context.WithCancel(l.core.ctx)
|
||||
var protocol linkProtocol
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "tcp":
|
||||
l.tcp.call(u.Host, tcpOpts, sintf)
|
||||
case "socks":
|
||||
tcpOpts.socksProxyAddr = u.Host
|
||||
if u.User != nil {
|
||||
tcpOpts.socksProxyAuth = &proxy.Auth{}
|
||||
tcpOpts.socksProxyAuth.User = u.User.Username()
|
||||
tcpOpts.socksProxyAuth.Password, _ = u.User.Password()
|
||||
}
|
||||
tcpOpts.upgrade = l.tcp.tls.forDialer // TODO make this configurable
|
||||
pathtokens := strings.Split(strings.Trim(u.Path, "/"), "/")
|
||||
l.tcp.call(pathtokens[0], tcpOpts, sintf)
|
||||
protocol = l.tcp
|
||||
case "tls":
|
||||
tcpOpts.upgrade = l.tcp.tls.forDialer
|
||||
l.tcp.call(u.Host, tcpOpts, sintf)
|
||||
protocol = l.tls
|
||||
case "unix":
|
||||
protocol = l.unix
|
||||
case "quic":
|
||||
protocol = l.quic
|
||||
case "ws":
|
||||
protocol = l.ws
|
||||
case "wss":
|
||||
protocol = l.wss
|
||||
default:
|
||||
return errors.New("unknown call scheme: " + u.Scheme)
|
||||
ctxcancel()
|
||||
return nil, ErrLinkUnrecognisedSchema
|
||||
}
|
||||
return nil
|
||||
listener, err := protocol.listen(ctx, u, sintf)
|
||||
if err != nil {
|
||||
ctxcancel()
|
||||
return nil, err
|
||||
}
|
||||
addr := listener.Addr()
|
||||
cancel := func() {
|
||||
ctxcancel()
|
||||
if err := listener.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
|
||||
l.core.log.Warnf("Error closing %s listener %s: %s", strings.ToUpper(u.Scheme), addr, err)
|
||||
}
|
||||
}
|
||||
li := &Listener{
|
||||
listener: listener,
|
||||
ctx: ctx,
|
||||
Cancel: cancel,
|
||||
}
|
||||
|
||||
var options linkOptions
|
||||
if p := u.Query().Get("priority"); p != "" {
|
||||
pi, err := strconv.ParseUint(p, 10, 8)
|
||||
if err != nil {
|
||||
return nil, ErrLinkPriorityInvalid
|
||||
}
|
||||
options.priority = uint8(pi)
|
||||
}
|
||||
if p := u.Query().Get("password"); p != "" {
|
||||
if len(p) > blake2b.Size {
|
||||
return nil, ErrLinkPasswordInvalid
|
||||
}
|
||||
options.password = []byte(p)
|
||||
}
|
||||
|
||||
phony.Block(l, func() {
|
||||
l._listeners[li] = cancel
|
||||
})
|
||||
|
||||
go func() {
|
||||
l.core.log.Infof("%s listener started on %s", strings.ToUpper(u.Scheme), addr)
|
||||
defer phony.Block(l, func() {
|
||||
cancel()
|
||||
delete(l._listeners, li)
|
||||
l.core.log.Infof("%s listener stopped on %s", strings.ToUpper(u.Scheme), addr)
|
||||
})
|
||||
for {
|
||||
conn, err := li.listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
// In order to populate a somewhat sane looking connection
|
||||
// URI in the admin socket, we need to replace the host in
|
||||
// the listener URL with the remote address.
|
||||
pu := *u
|
||||
pu.Host = conn.RemoteAddr().String()
|
||||
lu := urlForLinkInfo(pu)
|
||||
info := linkInfo{
|
||||
uri: lu.String(),
|
||||
sintf: sintf,
|
||||
}
|
||||
|
||||
// If there's an existing link state for this link, get it.
|
||||
// If this node is already connected to us, just drop the
|
||||
// connection. This prevents duplicate peerings.
|
||||
var lc *linkConn
|
||||
var state *link
|
||||
phony.Block(l, func() {
|
||||
var ok bool
|
||||
state, ok = l._links[info]
|
||||
if !ok || state == nil {
|
||||
state = &link{
|
||||
linkType: linkTypeIncoming,
|
||||
linkProto: strings.ToUpper(u.Scheme),
|
||||
kick: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
if state._conn != nil {
|
||||
// If a connection has come up in this time, abort
|
||||
// this one.
|
||||
return
|
||||
}
|
||||
|
||||
// The linkConn wrapper allows us to track the number of
|
||||
// bytes written to and read from this connection without
|
||||
// the help of ironwood.
|
||||
lc = &linkConn{
|
||||
Conn: conn,
|
||||
up: time.Now(),
|
||||
}
|
||||
|
||||
// Update the link state with our newly wrapped connection.
|
||||
// Clear the error state.
|
||||
state._conn = lc
|
||||
state._err = nil
|
||||
state._errtime = time.Time{}
|
||||
|
||||
// Store the state of the link so that it can be queried later.
|
||||
l._links[info] = state
|
||||
})
|
||||
defer phony.Block(l, func() {
|
||||
if l._links[info] == state {
|
||||
delete(l._links, info)
|
||||
}
|
||||
})
|
||||
if lc == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Give the connection to the handler. The handler will block
|
||||
// for the lifetime of the connection.
|
||||
switch err = l.handler(linkTypeIncoming, options, lc, nil, local); {
|
||||
case err == nil:
|
||||
case errors.Is(err, io.EOF):
|
||||
case errors.Is(err, net.ErrClosed):
|
||||
default:
|
||||
l.core.log.Debugf("Link %s error: %s\n", u.Host, err)
|
||||
}
|
||||
|
||||
// The handler has stopped running so the connection is dead,
|
||||
// try to close the underlying socket just in case and then
|
||||
// drop the link state.
|
||||
_ = lc.Close()
|
||||
}(conn)
|
||||
}
|
||||
}()
|
||||
return li, nil
|
||||
}
|
||||
|
||||
func (l *links) create(conn net.Conn, name, linkType, local, remote string, incoming, force bool, options linkOptions) (*link, error) {
|
||||
// Technically anything unique would work for names, but let's pick something human readable, just for debugging
|
||||
intf := link{
|
||||
conn: conn,
|
||||
lname: name,
|
||||
links: l,
|
||||
options: options,
|
||||
info: linkInfo{
|
||||
linkType: linkType,
|
||||
local: local,
|
||||
remote: remote,
|
||||
},
|
||||
incoming: incoming,
|
||||
force: force,
|
||||
func (l *links) connect(ctx context.Context, u *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||
var dialer linkProtocol
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "tcp":
|
||||
dialer = l.tcp
|
||||
case "tls":
|
||||
dialer = l.tls
|
||||
case "socks", "sockstls":
|
||||
dialer = l.socks
|
||||
case "unix":
|
||||
dialer = l.unix
|
||||
case "quic":
|
||||
dialer = l.quic
|
||||
case "ws":
|
||||
dialer = l.ws
|
||||
case "wss":
|
||||
dialer = l.wss
|
||||
default:
|
||||
return nil, ErrLinkUnrecognisedSchema
|
||||
}
|
||||
return &intf, nil
|
||||
return dialer.dial(ctx, u, info, options)
|
||||
}
|
||||
|
||||
func (l *links) stop() error {
|
||||
close(l.stopped)
|
||||
if err := l.tcp.stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (intf *link) handler() (chan struct{}, error) {
|
||||
// TODO split some of this into shorter functions, so it's easier to read, and for the FIXME duplicate peer issue mentioned later
|
||||
defer intf.conn.Close()
|
||||
func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn, success func(), local bool) error {
|
||||
meta := version_getBaseMetadata()
|
||||
meta.key = intf.links.core.public
|
||||
metaBytes := meta.encode()
|
||||
// TODO timeouts on send/recv (goroutine for send/recv, channel select w/ timer)
|
||||
var err error
|
||||
if !util.FuncTimeout(30*time.Second, func() {
|
||||
var n int
|
||||
n, err = intf.conn.Write(metaBytes)
|
||||
if err == nil && n != len(metaBytes) {
|
||||
err = errors.New("incomplete metadata send")
|
||||
}
|
||||
}) {
|
||||
return nil, errors.New("timeout on metadata send")
|
||||
}
|
||||
meta.publicKey = l.core.public
|
||||
meta.priority = options.priority
|
||||
metaBytes, err := meta.encode(l.core.secret, options.password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return fmt.Errorf("failed to generate handshake: %w", err)
|
||||
}
|
||||
if !util.FuncTimeout(30*time.Second, func() {
|
||||
var n int
|
||||
n, err = io.ReadFull(intf.conn, metaBytes)
|
||||
if err == nil && n != len(metaBytes) {
|
||||
err = errors.New("incomplete metadata recv")
|
||||
}
|
||||
}) {
|
||||
return nil, errors.New("timeout on metadata recv")
|
||||
if err := conn.SetDeadline(time.Now().Add(time.Second * 6)); err != nil {
|
||||
return fmt.Errorf("failed to set handshake deadline: %w", err)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
n, err := conn.Write(metaBytes)
|
||||
switch {
|
||||
case err != nil:
|
||||
return fmt.Errorf("write handshake: %w", err)
|
||||
case n != len(metaBytes):
|
||||
return fmt.Errorf("incomplete handshake send")
|
||||
}
|
||||
meta = version_metadata{}
|
||||
base := version_getBaseMetadata()
|
||||
if !meta.decode(metaBytes) {
|
||||
return nil, errors.New("failed to decode metadata")
|
||||
if err := meta.decode(conn, options.password); err != nil {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
}
|
||||
if !meta.check() {
|
||||
intf.links.core.log.Errorf("Failed to connect to node: %s is incompatible version (local %s, remote %s)",
|
||||
intf.lname,
|
||||
fmt.Sprintf("%d.%d", base.ver, base.minorVer),
|
||||
fmt.Sprintf("%d.%d", meta.ver, meta.minorVer),
|
||||
return fmt.Errorf("remote node incompatible version (local %s, remote %s)",
|
||||
fmt.Sprintf("%d.%d", base.majorVer, base.minorVer),
|
||||
fmt.Sprintf("%d.%d", meta.majorVer, meta.minorVer),
|
||||
)
|
||||
return nil, errors.New("remote node is incompatible version")
|
||||
}
|
||||
if err = conn.SetDeadline(time.Time{}); err != nil {
|
||||
return fmt.Errorf("failed to clear handshake deadline: %w", err)
|
||||
}
|
||||
// Check if the remote side matches the keys we expected. This is a bit of a weak
|
||||
// check - in future versions we really should check a signature or something like that.
|
||||
if pinned := intf.options.pinnedEd25519Keys; pinned != nil {
|
||||
if pinned := options.pinnedEd25519Keys; len(pinned) > 0 {
|
||||
var key keyArray
|
||||
copy(key[:], meta.key)
|
||||
copy(key[:], meta.publicKey)
|
||||
if _, allowed := pinned[key]; !allowed {
|
||||
intf.links.core.log.Errorf("Failed to connect to node: %q sent ed25519 key that does not match pinned keys", intf.name())
|
||||
return nil, fmt.Errorf("failed to connect: host sent ed25519 key that does not match pinned keys")
|
||||
return fmt.Errorf("node public key that does not match pinned keys")
|
||||
}
|
||||
}
|
||||
// Check if we're authorized to connect to this key / IP
|
||||
intf.links.core.config.RLock()
|
||||
allowed := intf.links.core.config.AllowedPublicKeys
|
||||
intf.links.core.config.RUnlock()
|
||||
isallowed := len(allowed) == 0
|
||||
for _, k := range allowed {
|
||||
if k == hex.EncodeToString(meta.key) { // TODO: this is yuck
|
||||
isallowed = true
|
||||
break
|
||||
if !local {
|
||||
var allowed map[[32]byte]struct{}
|
||||
phony.Block(l.core, func() {
|
||||
allowed = l.core.config._allowedPublicKeys
|
||||
})
|
||||
isallowed := len(allowed) == 0
|
||||
for k := range allowed {
|
||||
if bytes.Equal(k[:], meta.publicKey) {
|
||||
isallowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if linkType == linkTypeIncoming && !isallowed {
|
||||
return fmt.Errorf("node public key %q is not in AllowedPublicKeys", hex.EncodeToString(meta.publicKey))
|
||||
}
|
||||
}
|
||||
if intf.incoming && !intf.force && !isallowed {
|
||||
intf.links.core.log.Warnf("%s connection from %s forbidden: AllowedEncryptionPublicKeys does not contain key %s",
|
||||
strings.ToUpper(intf.info.linkType), intf.info.remote, hex.EncodeToString(meta.key))
|
||||
intf.close()
|
||||
return nil, nil
|
||||
|
||||
dir := "outbound"
|
||||
if linkType == linkTypeIncoming {
|
||||
dir = "inbound"
|
||||
}
|
||||
// Check if we already have a link to this node
|
||||
copy(intf.info.key[:], meta.key)
|
||||
intf.links.mutex.Lock()
|
||||
if oldIntf, isIn := intf.links.links[intf.info]; isIn {
|
||||
intf.links.mutex.Unlock()
|
||||
// FIXME we should really return an error and let the caller block instead
|
||||
// That lets them do things like close connections on its own, avoid printing a connection message in the first place, etc.
|
||||
intf.links.core.log.Debugln("DEBUG: found existing interface for", intf.name())
|
||||
return oldIntf.closed, nil
|
||||
} else {
|
||||
intf.closed = make(chan struct{})
|
||||
intf.links.links[intf.info] = intf
|
||||
defer func() {
|
||||
intf.links.mutex.Lock()
|
||||
delete(intf.links.links, intf.info)
|
||||
intf.links.mutex.Unlock()
|
||||
close(intf.closed)
|
||||
}()
|
||||
intf.links.core.log.Debugln("DEBUG: registered interface for", intf.name())
|
||||
remoteAddr := net.IP(address.AddrForKey(meta.publicKey)[:]).String()
|
||||
remoteStr := fmt.Sprintf("%s@%s", remoteAddr, conn.RemoteAddr())
|
||||
localStr := conn.LocalAddr()
|
||||
priority := options.priority
|
||||
if meta.priority > priority {
|
||||
priority = meta.priority
|
||||
}
|
||||
intf.links.mutex.Unlock()
|
||||
themAddr := address.AddrForKey(ed25519.PublicKey(intf.info.key[:]))
|
||||
themAddrString := net.IP(themAddr[:]).String()
|
||||
themString := fmt.Sprintf("%s@%s", themAddrString, intf.info.remote)
|
||||
intf.links.core.log.Infof("Connected %s: %s, source %s",
|
||||
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
|
||||
// Run the handler
|
||||
err = intf.links.core.pc.HandleConn(ed25519.PublicKey(intf.info.key[:]), intf.conn)
|
||||
// TODO don't report an error if it's just a 'use of closed network connection'
|
||||
l.core.log.Infof("Connected %s: %s, source %s",
|
||||
dir, remoteStr, localStr)
|
||||
if success != nil {
|
||||
success()
|
||||
}
|
||||
|
||||
err = l.core.HandleConn(meta.publicKey, conn, priority)
|
||||
switch err {
|
||||
case io.EOF, net.ErrClosed, nil:
|
||||
l.core.log.Infof("Disconnected %s: %s, source %s",
|
||||
dir, remoteStr, localStr)
|
||||
default:
|
||||
l.core.log.Infof("Disconnected %s: %s, source %s; error: %s",
|
||||
dir, remoteStr, localStr, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *links) findSuitableIP(url *url.URL, fn func(hostname string, ip net.IP, port int) (net.Conn, error)) (net.Conn, error) {
|
||||
host, p, err := net.SplitHostPort(url.Host)
|
||||
if err != nil {
|
||||
intf.links.core.log.Infof("Disconnected %s: %s, source %s; error: %s",
|
||||
strings.ToUpper(intf.info.linkType), themString, intf.info.local, err)
|
||||
} else {
|
||||
intf.links.core.log.Infof("Disconnected %s: %s, source %s",
|
||||
strings.ToUpper(intf.info.linkType), themString, intf.info.local)
|
||||
return nil, err
|
||||
}
|
||||
port, err := strconv.Atoi(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := net.LookupIP(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var _ips [64]net.IP
|
||||
ips := _ips[:0]
|
||||
for _, ip := range resp {
|
||||
switch {
|
||||
case ip.IsUnspecified():
|
||||
continue
|
||||
case ip.IsMulticast():
|
||||
continue
|
||||
case ip.IsLinkLocalMulticast():
|
||||
continue
|
||||
case ip.IsInterfaceLocalMulticast():
|
||||
continue
|
||||
case l.core.config.peerFilter != nil && !l.core.config.peerFilter(ip):
|
||||
continue
|
||||
}
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
return nil, ErrLinkNoSuitableIPs
|
||||
}
|
||||
for _, ip := range ips {
|
||||
var conn net.Conn
|
||||
if conn, err = fn(host, ip, port); err != nil {
|
||||
url := *url
|
||||
url.RawQuery = ""
|
||||
l.core.log.Debugln("Dialling", url.Redacted(), "reported error:", err)
|
||||
continue
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (intf *link) close() {
|
||||
intf.conn.Close()
|
||||
func urlForLinkInfo(u url.URL) url.URL {
|
||||
u.RawQuery = ""
|
||||
return u
|
||||
}
|
||||
|
||||
func (intf *link) name() string {
|
||||
return intf.lname
|
||||
type linkConn struct {
|
||||
// tx and rx are at the beginning of the struct to ensure 64-bit alignment
|
||||
// on 32-bit platforms, see https://pkg.go.dev/sync/atomic#pkg-note-BUG
|
||||
rx uint64
|
||||
tx uint64
|
||||
rxrate uint64
|
||||
txrate uint64
|
||||
lastrx uint64
|
||||
lasttx uint64
|
||||
up time.Time
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *linkConn) Read(p []byte) (n int, err error) {
|
||||
n, err = c.Conn.Read(p)
|
||||
atomic.AddUint64(&c.rx, uint64(n))
|
||||
return
|
||||
}
|
||||
|
||||
func (c *linkConn) Write(p []byte) (n int, err error) {
|
||||
n, err = c.Conn.Write(p)
|
||||
atomic.AddUint64(&c.tx, uint64(n))
|
||||
return
|
||||
}
|
||||
|
|
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
|
||||
}
|
67
src/core/link_socks.go
Normal file
67
src/core/link_socks.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
type linkSOCKS struct {
|
||||
*links
|
||||
}
|
||||
|
||||
func (l *links) newLinkSOCKS() *linkSOCKS {
|
||||
lt := &linkSOCKS{
|
||||
links: l,
|
||||
}
|
||||
return lt
|
||||
}
|
||||
|
||||
func (l *linkSOCKS) dial(_ context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||
var proxyAuth *proxy.Auth
|
||||
if url.User != nil && url.User.Username() != "" {
|
||||
proxyAuth = &proxy.Auth{
|
||||
User: url.User.Username(),
|
||||
}
|
||||
proxyAuth.Password, _ = url.User.Password()
|
||||
}
|
||||
tlsconfig := l.tls.config.Clone()
|
||||
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
|
||||
hostport := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
|
||||
dialer, err := l.tcp.dialerFor(&net.TCPAddr{
|
||||
IP: ip,
|
||||
Port: port,
|
||||
}, info.sintf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proxy, err := proxy.SOCKS5("tcp", hostport, proxyAuth, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/")
|
||||
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) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
||||
return nil, fmt.Errorf("SOCKS listener not supported")
|
||||
}
|
111
src/core/link_tcp.go
Normal file
111
src/core/link_tcp.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/Arceliar/phony"
|
||||
)
|
||||
|
||||
type linkTCP struct {
|
||||
phony.Inbox
|
||||
*links
|
||||
listenconfig *net.ListenConfig
|
||||
}
|
||||
|
||||
func (l *links) newLinkTCP() *linkTCP {
|
||||
lt := &linkTCP{
|
||||
links: l,
|
||||
listenconfig: &net.ListenConfig{
|
||||
KeepAlive: -1,
|
||||
},
|
||||
}
|
||||
lt.listenconfig.Control = lt.tcpContext
|
||||
return lt
|
||||
}
|
||||
|
||||
func (l *linkTCP) 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) {
|
||||
addr := &net.TCPAddr{
|
||||
IP: ip,
|
||||
Port: port,
|
||||
}
|
||||
dialer, err := l.tcp.dialerFor(addr, info.sintf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dialer.DialContext(ctx, "tcp", addr.String())
|
||||
})
|
||||
}
|
||||
|
||||
func (l *linkTCP) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
|
||||
hostport := url.Host
|
||||
if sintf != "" {
|
||||
if host, port, err := net.SplitHostPort(hostport); err == nil {
|
||||
hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port)
|
||||
}
|
||||
}
|
||||
return l.listenconfig.Listen(ctx, "tcp", hostport)
|
||||
}
|
||||
|
||||
func (l *linkTCP) dialerFor(dst *net.TCPAddr, sintf string) (*net.Dialer, error) {
|
||||
if dst.IP.IsLinkLocalUnicast() {
|
||||
if sintf != "" {
|
||||
dst.Zone = sintf
|
||||
}
|
||||
if dst.Zone == "" {
|
||||
return nil, fmt.Errorf("link-local address requires a zone")
|
||||
}
|
||||
}
|
||||
dialer := &net.Dialer{
|
||||
Timeout: time.Second * 5,
|
||||
KeepAlive: -1,
|
||||
Control: l.tcpContext,
|
||||
}
|
||||
if sintf != "" {
|
||||
dialer.Control = l.getControl(sintf)
|
||||
ief, err := net.InterfaceByName(sintf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("interface %q not found", sintf)
|
||||
}
|
||||
if ief.Flags&net.FlagUp == 0 {
|
||||
return nil, fmt.Errorf("interface %q is not up", sintf)
|
||||
}
|
||||
addrs, err := ief.Addrs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("interface %q addresses not available: %w", sintf, err)
|
||||
}
|
||||
for addrindex, addr := range addrs {
|
||||
src, _, err := net.ParseCIDR(addr.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !src.IsGlobalUnicast() && !src.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
bothglobal := src.IsGlobalUnicast() == dst.IP.IsGlobalUnicast()
|
||||
bothlinklocal := src.IsLinkLocalUnicast() == dst.IP.IsLinkLocalUnicast()
|
||||
if !bothglobal && !bothlinklocal {
|
||||
continue
|
||||
}
|
||||
if (src.To4() != nil) != (dst.IP.To4() != nil) {
|
||||
continue
|
||||
}
|
||||
if bothglobal || bothlinklocal || addrindex == len(addrs)-1 {
|
||||
dialer.LocalAddr = &net.TCPAddr{
|
||||
IP: src,
|
||||
Port: 0,
|
||||
Zone: sintf,
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if dialer.LocalAddr == nil {
|
||||
return nil, fmt.Errorf("no suitable source address found on interface %q", sintf)
|
||||
}
|
||||
}
|
||||
return dialer, nil
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package core
|
||||
|
@ -10,7 +11,7 @@ import (
|
|||
|
||||
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
|
||||
|
||||
func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error {
|
||||
func (t *linkTCP) tcpContext(network, address string, c syscall.RawConn) error {
|
||||
var control error
|
||||
var recvanyif error
|
||||
|
||||
|
@ -27,6 +28,6 @@ func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *tcp) getControl(sintf string) func(string, string, syscall.RawConn) error {
|
||||
func (t *linkTCP) getControl(_ string) func(string, string, syscall.RawConn) error {
|
||||
return t.tcpContext
|
||||
}
|
30
src/core/link_tcp_linux.go
Normal file
30
src/core/link_tcp_linux.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *linkTCP) getControl(sintf string) func(string, string, syscall.RawConn) error {
|
||||
return func(network, address string, c syscall.RawConn) error {
|
||||
var err error
|
||||
btd := func(fd uintptr) {
|
||||
err = unix.BindToDevice(int(fd), sintf)
|
||||
}
|
||||
_ = c.Control(btd)
|
||||
if err != nil {
|
||||
t.links.core.log.Debugln("Failed to set SO_BINDTODEVICE:", sintf)
|
||||
}
|
||||
return t.tcpContext(network, address, c)
|
||||
}
|
||||
}
|
18
src/core/link_tcp_other.go
Normal file
18
src/core/link_tcp_other.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
//go:build !darwin && !linux
|
||||
// +build !darwin,!linux
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *linkTCP) getControl(sintf string) func(string, string, syscall.RawConn) error {
|
||||
return t.tcpContext
|
||||
}
|
72
src/core/link_tls.go
Normal file
72
src/core/link_tls.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
"github.com/Arceliar/phony"
|
||||
)
|
||||
|
||||
type linkTLS struct {
|
||||
phony.Inbox
|
||||
*links
|
||||
tcp *linkTCP
|
||||
listener *net.ListenConfig
|
||||
config *tls.Config
|
||||
}
|
||||
|
||||
func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS {
|
||||
lt := &linkTLS{
|
||||
links: l,
|
||||
tcp: tcp,
|
||||
listener: &net.ListenConfig{
|
||||
Control: tcp.tcpContext,
|
||||
KeepAlive: -1,
|
||||
},
|
||||
config: l.core.config.tls.Clone(),
|
||||
}
|
||||
return lt
|
||||
}
|
||||
|
||||
func (l *linkTLS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||
tlsconfig := l.config.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
|
||||
if sni := options.tlsSNI; sni != "" {
|
||||
tlsconfig.ServerName = sni
|
||||
}
|
||||
addr := &net.TCPAddr{
|
||||
IP: ip,
|
||||
Port: port,
|
||||
}
|
||||
dialer, err := l.tcp.dialerFor(addr, info.sintf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsdialer := &tls.Dialer{
|
||||
NetDialer: dialer,
|
||||
Config: tlsconfig,
|
||||
}
|
||||
return tlsdialer.DialContext(ctx, "tcp", addr.String())
|
||||
})
|
||||
}
|
||||
|
||||
func (l *linkTLS) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
|
||||
hostport := url.Host
|
||||
if sintf != "" {
|
||||
if host, port, err := net.SplitHostPort(hostport); err == nil {
|
||||
hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port)
|
||||
}
|
||||
}
|
||||
listener, err := l.listener.Listen(ctx, "tcp", hostport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlslistener := tls.NewListener(listener, l.config)
|
||||
return tlslistener, nil
|
||||
}
|
43
src/core/link_unix.go
Normal file
43
src/core/link_unix.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/Arceliar/phony"
|
||||
)
|
||||
|
||||
type linkUNIX struct {
|
||||
phony.Inbox
|
||||
*links
|
||||
dialer *net.Dialer
|
||||
listener *net.ListenConfig
|
||||
}
|
||||
|
||||
func (l *links) newLinkUNIX() *linkUNIX {
|
||||
lt := &linkUNIX{
|
||||
links: l,
|
||||
dialer: &net.Dialer{
|
||||
Timeout: time.Second * 5,
|
||||
KeepAlive: -1,
|
||||
},
|
||||
listener: &net.ListenConfig{
|
||||
KeepAlive: -1,
|
||||
},
|
||||
}
|
||||
return lt
|
||||
}
|
||||
|
||||
func (l *linkUNIX) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
|
||||
addr, err := net.ResolveUnixAddr("unix", url.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l.dialer.DialContext(ctx, "unix", addr.String())
|
||||
}
|
||||
|
||||
func (l *linkUNIX) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
|
||||
return l.listener.Listen(ctx, "unix", url.Path)
|
||||
}
|
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")
|
||||
}
|
|
@ -4,31 +4,24 @@ import (
|
|||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
iwt "github.com/Arceliar/ironwood/types"
|
||||
"github.com/Arceliar/phony"
|
||||
|
||||
//"github.com/yggdrasil-network/yggdrasil-go/src/crypto"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/version"
|
||||
)
|
||||
|
||||
// NodeInfoPayload represents a RequestNodeInfo response, in bytes.
|
||||
type NodeInfoPayload []byte
|
||||
|
||||
type nodeinfo struct {
|
||||
phony.Inbox
|
||||
proto *protoHandler
|
||||
myNodeInfo NodeInfoPayload
|
||||
myNodeInfo json.RawMessage
|
||||
callbacks map[keyArray]nodeinfoCallback
|
||||
}
|
||||
|
||||
type nodeinfoCallback struct {
|
||||
call func(nodeinfo NodeInfoPayload)
|
||||
call func(nodeinfo json.RawMessage)
|
||||
created time.Time
|
||||
}
|
||||
|
||||
|
@ -57,7 +50,7 @@ func (m *nodeinfo) _cleanup() {
|
|||
})
|
||||
}
|
||||
|
||||
func (m *nodeinfo) _addCallback(sender keyArray, call func(nodeinfo NodeInfoPayload)) {
|
||||
func (m *nodeinfo) _addCallback(sender keyArray, call func(nodeinfo json.RawMessage)) {
|
||||
m.callbacks[sender] = nodeinfoCallback{
|
||||
created: time.Now(),
|
||||
call: call,
|
||||
|
@ -65,71 +58,59 @@ func (m *nodeinfo) _addCallback(sender keyArray, call func(nodeinfo NodeInfoPayl
|
|||
}
|
||||
|
||||
// Handles the callback, if there is one
|
||||
func (m *nodeinfo) _callback(sender keyArray, nodeinfo NodeInfoPayload) {
|
||||
func (m *nodeinfo) _callback(sender keyArray, nodeinfo json.RawMessage) {
|
||||
if callback, ok := m.callbacks[sender]; ok {
|
||||
callback.call(nodeinfo)
|
||||
delete(m.callbacks, sender)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *nodeinfo) _getNodeInfo() NodeInfoPayload {
|
||||
func (m *nodeinfo) _getNodeInfo() json.RawMessage {
|
||||
return m.myNodeInfo
|
||||
}
|
||||
|
||||
// Set the current node's nodeinfo
|
||||
func (m *nodeinfo) setNodeInfo(given interface{}, privacy bool) (err error) {
|
||||
func (m *nodeinfo) setNodeInfo(given map[string]interface{}, privacy bool) (err error) {
|
||||
phony.Block(m, func() {
|
||||
err = m._setNodeInfo(given, privacy)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (m *nodeinfo) _setNodeInfo(given interface{}, privacy bool) error {
|
||||
defaults := map[string]interface{}{
|
||||
"buildname": version.BuildName(),
|
||||
"buildversion": version.BuildVersion(),
|
||||
"buildplatform": runtime.GOOS,
|
||||
"buildarch": runtime.GOARCH,
|
||||
func (m *nodeinfo) _setNodeInfo(given map[string]interface{}, privacy bool) error {
|
||||
newnodeinfo := make(map[string]interface{}, len(given))
|
||||
for k, v := range given {
|
||||
newnodeinfo[k] = v
|
||||
}
|
||||
newnodeinfo := make(map[string]interface{})
|
||||
if !privacy {
|
||||
for k, v := range defaults {
|
||||
newnodeinfo[k] = v
|
||||
}
|
||||
}
|
||||
if nodeinfomap, ok := given.(map[string]interface{}); ok {
|
||||
for key, value := range nodeinfomap {
|
||||
if _, ok := defaults[key]; ok {
|
||||
if strvalue, strok := value.(string); strok && strings.EqualFold(strvalue, "null") || value == nil {
|
||||
delete(newnodeinfo, key)
|
||||
}
|
||||
continue
|
||||
}
|
||||
newnodeinfo[key] = value
|
||||
}
|
||||
newnodeinfo["buildname"] = version.BuildName()
|
||||
newnodeinfo["buildversion"] = version.BuildVersion()
|
||||
newnodeinfo["buildplatform"] = runtime.GOOS
|
||||
newnodeinfo["buildarch"] = runtime.GOARCH
|
||||
}
|
||||
newjson, err := json.Marshal(newnodeinfo)
|
||||
if err == nil {
|
||||
if len(newjson) > 16384 {
|
||||
return errors.New("NodeInfo exceeds max length of 16384 bytes")
|
||||
}
|
||||
switch {
|
||||
case err != nil:
|
||||
return fmt.Errorf("NodeInfo marshalling failed: %w", err)
|
||||
case len(newjson) > 16384:
|
||||
return fmt.Errorf("NodeInfo exceeds max length of 16384 bytes")
|
||||
default:
|
||||
m.myNodeInfo = newjson
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *nodeinfo) sendReq(from phony.Actor, key keyArray, callback func(nodeinfo NodeInfoPayload)) {
|
||||
func (m *nodeinfo) sendReq(from phony.Actor, key keyArray, callback func(nodeinfo json.RawMessage)) {
|
||||
m.Act(from, func() {
|
||||
m._sendReq(key, callback)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *nodeinfo) _sendReq(key keyArray, callback func(nodeinfo NodeInfoPayload)) {
|
||||
func (m *nodeinfo) _sendReq(key keyArray, callback func(nodeinfo json.RawMessage)) {
|
||||
if callback != nil {
|
||||
m._addCallback(key, callback)
|
||||
}
|
||||
_, _ = m.proto.core.pc.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, iwt.Addr(key[:]))
|
||||
_, _ = m.proto.core.PacketConn.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, iwt.Addr(key[:]))
|
||||
}
|
||||
|
||||
func (m *nodeinfo) handleReq(from phony.Actor, key keyArray) {
|
||||
|
@ -138,7 +119,7 @@ func (m *nodeinfo) handleReq(from phony.Actor, key keyArray) {
|
|||
})
|
||||
}
|
||||
|
||||
func (m *nodeinfo) handleRes(from phony.Actor, key keyArray, info NodeInfoPayload) {
|
||||
func (m *nodeinfo) handleRes(from phony.Actor, key keyArray, info json.RawMessage) {
|
||||
m.Act(from, func() {
|
||||
m._callback(key, info)
|
||||
})
|
||||
|
@ -146,7 +127,7 @@ func (m *nodeinfo) handleRes(from phony.Actor, key keyArray, info NodeInfoPayloa
|
|||
|
||||
func (m *nodeinfo) _sendRes(key keyArray) {
|
||||
bs := append([]byte{typeSessionProto, typeProtoNodeInfoResponse}, m._getNodeInfo()...)
|
||||
_, _ = m.proto.core.pc.WriteTo(bs, iwt.Addr(key[:]))
|
||||
_, _ = m.proto.core.PacketConn.WriteTo(bs, iwt.Addr(key[:]))
|
||||
}
|
||||
|
||||
// Admin socket stuff
|
||||
|
@ -154,36 +135,39 @@ func (m *nodeinfo) _sendRes(key keyArray) {
|
|||
type GetNodeInfoRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
type GetNodeInfoResponse map[string]interface{}
|
||||
type GetNodeInfoResponse map[string]json.RawMessage
|
||||
|
||||
func (m *nodeinfo) nodeInfoAdminHandler(in json.RawMessage) (interface{}, error) {
|
||||
var req GetNodeInfoRequest
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.Key == "" {
|
||||
return nil, fmt.Errorf("No remote public key supplied")
|
||||
}
|
||||
var key keyArray
|
||||
var kbs []byte
|
||||
var err error
|
||||
if kbs, err = hex.DecodeString(req.Key); err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("Failed to decode public key: %w", err)
|
||||
}
|
||||
copy(key[:], kbs)
|
||||
ch := make(chan []byte, 1)
|
||||
m.sendReq(nil, key, func(info NodeInfoPayload) {
|
||||
m.sendReq(nil, key, func(info json.RawMessage) {
|
||||
ch <- info
|
||||
})
|
||||
timer := time.NewTimer(6 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-timer.C:
|
||||
return nil, errors.New("timeout")
|
||||
return nil, errors.New("Timed out waiting for response")
|
||||
case info := <-ch:
|
||||
var msg json.RawMessage
|
||||
if err := msg.UnmarshalJSON(info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ip := net.IP(address.AddrForKey(kbs)[:])
|
||||
res := GetNodeInfoResponse{ip.String(): msg}
|
||||
key := hex.EncodeToString(kbs[:])
|
||||
res := GetNodeInfoResponse{key: msg}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
|
61
src/core/options.go
Normal file
61
src/core/options.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func (c *Core) _applyOption(opt SetupOption) (err error) {
|
||||
switch v := opt.(type) {
|
||||
case Peer:
|
||||
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:
|
||||
c.config._listeners[v] = struct{}{}
|
||||
case PeerFilter:
|
||||
c.config.peerFilter = v
|
||||
case NodeInfo:
|
||||
c.config.nodeinfo = v
|
||||
case NodeInfoPrivacy:
|
||||
c.config.nodeinfoPrivacy = v
|
||||
case AllowedPublicKey:
|
||||
pk := [32]byte{}
|
||||
copy(pk[:], v)
|
||||
c.config._allowedPublicKeys[pk] = struct{}{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type SetupOption interface {
|
||||
isSetupOption()
|
||||
}
|
||||
|
||||
type ListenAddress string
|
||||
type Peer struct {
|
||||
URI string
|
||||
SourceInterface string
|
||||
}
|
||||
type NodeInfo map[string]interface{}
|
||||
type NodeInfoPrivacy bool
|
||||
type AllowedPublicKey ed25519.PublicKey
|
||||
type PeerFilter func(net.IP) bool
|
||||
|
||||
func (a ListenAddress) isSetupOption() {}
|
||||
func (a Peer) isSetupOption() {}
|
||||
func (a NodeInfo) isSetupOption() {}
|
||||
func (a NodeInfoPrivacy) 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
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
@ -20,8 +21,8 @@ const (
|
|||
typeDebugGetSelfResponse
|
||||
typeDebugGetPeersRequest
|
||||
typeDebugGetPeersResponse
|
||||
typeDebugGetDHTRequest
|
||||
typeDebugGetDHTResponse
|
||||
typeDebugGetTreeRequest
|
||||
typeDebugGetTreeResponse
|
||||
)
|
||||
|
||||
type reqInfo struct {
|
||||
|
@ -29,23 +30,30 @@ type reqInfo struct {
|
|||
timer *time.Timer // time.AfterFunc cleanup
|
||||
}
|
||||
|
||||
type keyArray [ed25519.PublicKeySize]byte
|
||||
|
||||
type protoHandler struct {
|
||||
phony.Inbox
|
||||
|
||||
core *Core
|
||||
nodeinfo nodeinfo
|
||||
sreqs map[keyArray]*reqInfo
|
||||
preqs map[keyArray]*reqInfo
|
||||
dreqs map[keyArray]*reqInfo
|
||||
|
||||
selfRequests map[keyArray]*reqInfo
|
||||
peersRequests map[keyArray]*reqInfo
|
||||
treeRequests map[keyArray]*reqInfo
|
||||
}
|
||||
|
||||
func (p *protoHandler) init(core *Core) {
|
||||
p.core = core
|
||||
p.nodeinfo.init(p)
|
||||
p.sreqs = make(map[keyArray]*reqInfo)
|
||||
p.preqs = make(map[keyArray]*reqInfo)
|
||||
p.dreqs = make(map[keyArray]*reqInfo)
|
||||
|
||||
p.selfRequests = make(map[keyArray]*reqInfo)
|
||||
p.peersRequests = make(map[keyArray]*reqInfo)
|
||||
p.treeRequests = make(map[keyArray]*reqInfo)
|
||||
}
|
||||
|
||||
// Common functions
|
||||
|
||||
func (p *protoHandler) handleProto(from phony.Actor, key keyArray, bs []byte) {
|
||||
if len(bs) == 0 {
|
||||
return
|
||||
|
@ -57,10 +65,16 @@ func (p *protoHandler) handleProto(from phony.Actor, key keyArray, bs []byte) {
|
|||
case typeProtoNodeInfoResponse:
|
||||
p.nodeinfo.handleRes(p, key, bs[1:])
|
||||
case typeProtoDebug:
|
||||
p._handleDebug(key, bs[1:])
|
||||
p.handleDebug(from, key, bs[1:])
|
||||
}
|
||||
}
|
||||
|
||||
func (p *protoHandler) handleDebug(from phony.Actor, key keyArray, bs []byte) {
|
||||
p.Act(from, func() {
|
||||
p._handleDebug(key, bs)
|
||||
})
|
||||
}
|
||||
|
||||
func (p *protoHandler) _handleDebug(key keyArray, bs []byte) {
|
||||
if len(bs) == 0 {
|
||||
return
|
||||
|
@ -75,29 +89,36 @@ func (p *protoHandler) _handleDebug(key keyArray, bs []byte) {
|
|||
p._handleGetPeersRequest(key)
|
||||
case typeDebugGetPeersResponse:
|
||||
p._handleGetPeersResponse(key, bs[1:])
|
||||
case typeDebugGetDHTRequest:
|
||||
p._handleGetDHTRequest(key)
|
||||
case typeDebugGetDHTResponse:
|
||||
p._handleGetDHTResponse(key, bs[1:])
|
||||
case typeDebugGetTreeRequest:
|
||||
p._handleGetTreeRequest(key)
|
||||
case typeDebugGetTreeResponse:
|
||||
p._handleGetTreeResponse(key, bs[1:])
|
||||
}
|
||||
}
|
||||
|
||||
func (p *protoHandler) _sendDebug(key keyArray, dType uint8, data []byte) {
|
||||
bs := append([]byte{typeSessionProto, typeProtoDebug, dType}, data...)
|
||||
_, _ = p.core.PacketConn.WriteTo(bs, iwt.Addr(key[:]))
|
||||
}
|
||||
|
||||
// Get self
|
||||
|
||||
func (p *protoHandler) sendGetSelfRequest(key keyArray, callback func([]byte)) {
|
||||
p.Act(nil, func() {
|
||||
if info := p.sreqs[key]; info != nil {
|
||||
if info := p.selfRequests[key]; info != nil {
|
||||
info.timer.Stop()
|
||||
delete(p.sreqs, key)
|
||||
delete(p.selfRequests, key)
|
||||
}
|
||||
info := new(reqInfo)
|
||||
info.callback = callback
|
||||
info.timer = time.AfterFunc(time.Minute, func() {
|
||||
p.Act(nil, func() {
|
||||
if p.sreqs[key] == info {
|
||||
delete(p.sreqs, key)
|
||||
if p.selfRequests[key] == info {
|
||||
delete(p.selfRequests, key)
|
||||
}
|
||||
})
|
||||
})
|
||||
p.sreqs[key] = info
|
||||
p.selfRequests[key] = info
|
||||
p._sendDebug(key, typeDebugGetSelfRequest, nil)
|
||||
})
|
||||
}
|
||||
|
@ -105,8 +126,8 @@ func (p *protoHandler) sendGetSelfRequest(key keyArray, callback func([]byte)) {
|
|||
func (p *protoHandler) _handleGetSelfRequest(key keyArray) {
|
||||
self := p.core.GetSelf()
|
||||
res := map[string]string{
|
||||
"key": hex.EncodeToString(self.Key[:]),
|
||||
"coords": fmt.Sprintf("%v", self.Coords),
|
||||
"key": hex.EncodeToString(self.Key[:]),
|
||||
"routing_entries": fmt.Sprintf("%v", self.RoutingEntries),
|
||||
}
|
||||
bs, err := json.Marshal(res) // FIXME this puts keys in base64, not hex
|
||||
if err != nil {
|
||||
|
@ -116,29 +137,31 @@ func (p *protoHandler) _handleGetSelfRequest(key keyArray) {
|
|||
}
|
||||
|
||||
func (p *protoHandler) _handleGetSelfResponse(key keyArray, bs []byte) {
|
||||
if info := p.sreqs[key]; info != nil {
|
||||
if info := p.selfRequests[key]; info != nil {
|
||||
info.timer.Stop()
|
||||
info.callback(bs)
|
||||
delete(p.sreqs, key)
|
||||
delete(p.selfRequests, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Get peers
|
||||
|
||||
func (p *protoHandler) sendGetPeersRequest(key keyArray, callback func([]byte)) {
|
||||
p.Act(nil, func() {
|
||||
if info := p.preqs[key]; info != nil {
|
||||
if info := p.peersRequests[key]; info != nil {
|
||||
info.timer.Stop()
|
||||
delete(p.preqs, key)
|
||||
delete(p.peersRequests, key)
|
||||
}
|
||||
info := new(reqInfo)
|
||||
info.callback = callback
|
||||
info.timer = time.AfterFunc(time.Minute, func() {
|
||||
p.Act(nil, func() {
|
||||
if p.preqs[key] == info {
|
||||
delete(p.preqs, key)
|
||||
if p.peersRequests[key] == info {
|
||||
delete(p.peersRequests, key)
|
||||
}
|
||||
})
|
||||
})
|
||||
p.preqs[key] = info
|
||||
p.peersRequests[key] = info
|
||||
p._sendDebug(key, typeDebugGetPeersRequest, nil)
|
||||
})
|
||||
}
|
||||
|
@ -149,7 +172,7 @@ func (p *protoHandler) _handleGetPeersRequest(key keyArray) {
|
|||
for _, pinfo := range peers {
|
||||
tmp := append(bs, pinfo.Key[:]...)
|
||||
const responseOverhead = 2 // 1 debug type, 1 getpeers type
|
||||
if uint64(len(tmp))+responseOverhead > p.core.store.maxSessionMTU() {
|
||||
if uint64(len(tmp))+responseOverhead > p.core.MTU() {
|
||||
break
|
||||
}
|
||||
bs = tmp
|
||||
|
@ -158,61 +181,58 @@ func (p *protoHandler) _handleGetPeersRequest(key keyArray) {
|
|||
}
|
||||
|
||||
func (p *protoHandler) _handleGetPeersResponse(key keyArray, bs []byte) {
|
||||
if info := p.preqs[key]; info != nil {
|
||||
if info := p.peersRequests[key]; info != nil {
|
||||
info.timer.Stop()
|
||||
info.callback(bs)
|
||||
delete(p.preqs, key)
|
||||
delete(p.peersRequests, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *protoHandler) sendGetDHTRequest(key keyArray, callback func([]byte)) {
|
||||
// Get Tree
|
||||
|
||||
func (p *protoHandler) sendGetTreeRequest(key keyArray, callback func([]byte)) {
|
||||
p.Act(nil, func() {
|
||||
if info := p.dreqs[key]; info != nil {
|
||||
if info := p.treeRequests[key]; info != nil {
|
||||
info.timer.Stop()
|
||||
delete(p.dreqs, key)
|
||||
delete(p.treeRequests, key)
|
||||
}
|
||||
info := new(reqInfo)
|
||||
info.callback = callback
|
||||
info.timer = time.AfterFunc(time.Minute, func() {
|
||||
p.Act(nil, func() {
|
||||
if p.dreqs[key] == info {
|
||||
delete(p.dreqs, key)
|
||||
if p.treeRequests[key] == info {
|
||||
delete(p.treeRequests, key)
|
||||
}
|
||||
})
|
||||
})
|
||||
p.dreqs[key] = info
|
||||
p._sendDebug(key, typeDebugGetDHTRequest, nil)
|
||||
p.treeRequests[key] = info
|
||||
p._sendDebug(key, typeDebugGetTreeRequest, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func (p *protoHandler) _handleGetDHTRequest(key keyArray) {
|
||||
dinfos := p.core.GetDHT()
|
||||
func (p *protoHandler) _handleGetTreeRequest(key keyArray) {
|
||||
dinfos := p.core.GetTree()
|
||||
var bs []byte
|
||||
for _, dinfo := range dinfos {
|
||||
tmp := append(bs, dinfo.Key[:]...)
|
||||
const responseOverhead = 2 // 1 debug type, 1 getdht type
|
||||
if uint64(len(tmp))+responseOverhead > p.core.store.maxSessionMTU() {
|
||||
const responseOverhead = 2 // 1 debug type, 1 gettree type
|
||||
if uint64(len(tmp))+responseOverhead > p.core.MTU() {
|
||||
break
|
||||
}
|
||||
bs = tmp
|
||||
}
|
||||
p._sendDebug(key, typeDebugGetDHTResponse, bs)
|
||||
p._sendDebug(key, typeDebugGetTreeResponse, bs)
|
||||
}
|
||||
|
||||
func (p *protoHandler) _handleGetDHTResponse(key keyArray, bs []byte) {
|
||||
if info := p.dreqs[key]; info != nil {
|
||||
func (p *protoHandler) _handleGetTreeResponse(key keyArray, bs []byte) {
|
||||
if info := p.treeRequests[key]; info != nil {
|
||||
info.timer.Stop()
|
||||
info.callback(bs)
|
||||
delete(p.dreqs, key)
|
||||
delete(p.treeRequests, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *protoHandler) _sendDebug(key keyArray, dType uint8, data []byte) {
|
||||
bs := append([]byte{typeSessionProto, typeProtoDebug, dType}, data...)
|
||||
_, _ = p.core.pc.WriteTo(bs, iwt.Addr(key[:]))
|
||||
}
|
||||
|
||||
// Admin socket stuff
|
||||
// Admin socket stuff for "Get self"
|
||||
|
||||
type DebugGetSelfRequest struct {
|
||||
Key string `json:"key"`
|
||||
|
@ -231,15 +251,16 @@ func (p *protoHandler) getSelfHandler(in json.RawMessage) (interface{}, error) {
|
|||
if kbs, err = hex.DecodeString(req.Key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(kbs) != ed25519.PublicKeySize {
|
||||
return nil, fmt.Errorf("invalid public key length")
|
||||
}
|
||||
copy(key[:], kbs)
|
||||
ch := make(chan []byte, 1)
|
||||
p.sendGetSelfRequest(key, func(info []byte) {
|
||||
ch <- info
|
||||
})
|
||||
timer := time.NewTimer(6 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-time.After(6 * time.Second):
|
||||
return nil, errors.New("timeout")
|
||||
case info := <-ch:
|
||||
var msg json.RawMessage
|
||||
|
@ -252,6 +273,8 @@ func (p *protoHandler) getSelfHandler(in json.RawMessage) (interface{}, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Admin socket stuff for "Get peers"
|
||||
|
||||
type DebugGetPeersRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
@ -269,15 +292,16 @@ func (p *protoHandler) getPeersHandler(in json.RawMessage) (interface{}, error)
|
|||
if kbs, err = hex.DecodeString(req.Key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(kbs) != ed25519.PublicKeySize {
|
||||
return nil, fmt.Errorf("invalid public key length")
|
||||
}
|
||||
copy(key[:], kbs)
|
||||
ch := make(chan []byte, 1)
|
||||
p.sendGetPeersRequest(key, func(info []byte) {
|
||||
ch <- info
|
||||
})
|
||||
timer := time.NewTimer(6 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-time.After(6 * time.Second):
|
||||
return nil, errors.New("timeout")
|
||||
case info := <-ch:
|
||||
ks := make(map[string][]string)
|
||||
|
@ -300,14 +324,16 @@ func (p *protoHandler) getPeersHandler(in json.RawMessage) (interface{}, error)
|
|||
}
|
||||
}
|
||||
|
||||
type DebugGetDHTRequest struct {
|
||||
// Admin socket stuff for "Get Tree"
|
||||
|
||||
type DebugGetTreeRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type DebugGetDHTResponse map[string]interface{}
|
||||
type DebugGetTreeResponse map[string]interface{}
|
||||
|
||||
func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) {
|
||||
var req DebugGetDHTRequest
|
||||
func (p *protoHandler) getTreeHandler(in json.RawMessage) (interface{}, error) {
|
||||
var req DebugGetTreeRequest
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -317,15 +343,16 @@ func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) {
|
|||
if kbs, err = hex.DecodeString(req.Key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(kbs) != ed25519.PublicKeySize {
|
||||
return nil, fmt.Errorf("invalid public key length")
|
||||
}
|
||||
copy(key[:], kbs)
|
||||
ch := make(chan []byte, 1)
|
||||
p.sendGetDHTRequest(key, func(info []byte) {
|
||||
p.sendGetTreeRequest(key, func(info []byte) {
|
||||
ch <- info
|
||||
})
|
||||
timer := time.NewTimer(6 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-time.After(6 * time.Second):
|
||||
return nil, errors.New("timeout")
|
||||
case info := <-ch:
|
||||
ks := make(map[string][]string)
|
||||
|
@ -343,7 +370,7 @@ func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) {
|
|||
return nil, err
|
||||
}
|
||||
ip := net.IP(address.AddrForKey(kbs)[:])
|
||||
res := DebugGetDHTResponse{ip.String(): msg}
|
||||
res := DebugGetTreeResponse{ip.String(): msg}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
|
417
src/core/tcp.go
417
src/core/tcp.go
|
@ -1,417 +0,0 @@
|
|||
package core
|
||||
|
||||
// This sends packets to peers using TCP as a transport
|
||||
// It's generally better tested than the UDP implementation
|
||||
// Using it regularly is insane, but I find TCP easier to test/debug with it
|
||||
// Updating and optimizing the UDP version is a higher priority
|
||||
|
||||
// TODO:
|
||||
// Something needs to make sure we're getting *valid* packets
|
||||
// Could be used to DoS (connect, give someone else's keys, spew garbage)
|
||||
// I guess the "peer" part should watch for link packets, disconnect?
|
||||
|
||||
// TCP connections start with a metadata exchange.
|
||||
// It involves exchanging version numbers and crypto keys
|
||||
// See version.go for version metadata format
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/proxy"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
//"github.com/yggdrasil-network/yggdrasil-go/src/util"
|
||||
)
|
||||
|
||||
const default_timeout = 6 * time.Second
|
||||
|
||||
// The TCP listener and information about active TCP connections, to avoid duplication.
|
||||
type tcp struct {
|
||||
links *links
|
||||
waitgroup sync.WaitGroup
|
||||
mutex sync.Mutex // Protecting the below
|
||||
listeners map[string]*TcpListener
|
||||
calls map[string]struct{}
|
||||
conns map[linkInfo](chan struct{})
|
||||
tls tcptls
|
||||
}
|
||||
|
||||
// TcpListener is a stoppable TCP listener interface. These are typically
|
||||
// returned from calls to the ListenTCP() function and are also used internally
|
||||
// to represent listeners created by the "Listen" configuration option and for
|
||||
// multicast interfaces.
|
||||
type TcpListener struct {
|
||||
Listener net.Listener
|
||||
opts tcpOptions
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
type TcpUpgrade struct {
|
||||
upgrade func(c net.Conn, o *tcpOptions) (net.Conn, error)
|
||||
name string
|
||||
}
|
||||
|
||||
type tcpOptions struct {
|
||||
linkOptions
|
||||
upgrade *TcpUpgrade
|
||||
socksProxyAddr string
|
||||
socksProxyAuth *proxy.Auth
|
||||
socksPeerAddr string
|
||||
}
|
||||
|
||||
func (l *TcpListener) Stop() {
|
||||
defer func() { _ = recover() }()
|
||||
close(l.stop)
|
||||
}
|
||||
|
||||
// Wrapper function to set additional options for specific connection types.
|
||||
func (t *tcp) setExtraOptions(c net.Conn) {
|
||||
switch sock := c.(type) {
|
||||
case *net.TCPConn:
|
||||
_ = sock.SetNoDelay(true)
|
||||
// TODO something for socks5
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the address of the listener.
|
||||
func (t *tcp) 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
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
for _, l := range t.listeners {
|
||||
return l.Listener.Addr().(*net.TCPAddr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initializes the struct.
|
||||
func (t *tcp) init(l *links) error {
|
||||
t.links = l
|
||||
t.tls.init(t)
|
||||
t.mutex.Lock()
|
||||
t.calls = make(map[string]struct{})
|
||||
t.conns = make(map[linkInfo](chan struct{}))
|
||||
t.listeners = make(map[string]*TcpListener)
|
||||
t.mutex.Unlock()
|
||||
|
||||
t.links.core.config.RLock()
|
||||
defer t.links.core.config.RUnlock()
|
||||
for _, listenaddr := range t.links.core.config.Listen {
|
||||
u, err := url.Parse(listenaddr)
|
||||
if err != nil {
|
||||
t.links.core.log.Errorln("Failed to parse listener: listener", listenaddr, "is not correctly formatted, ignoring")
|
||||
}
|
||||
if _, err := t.listenURL(u, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tcp) stop() error {
|
||||
t.mutex.Lock()
|
||||
for _, listener := range t.listeners {
|
||||
listener.Stop()
|
||||
}
|
||||
t.mutex.Unlock()
|
||||
t.waitgroup.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tcp) listenURL(u *url.URL, sintf string) (*TcpListener, error) {
|
||||
var listener *TcpListener
|
||||
var err error
|
||||
hostport := u.Host // Used for tcp and tls
|
||||
if len(sintf) != 0 {
|
||||
host, port, err := net.SplitHostPort(hostport)
|
||||
if err == nil {
|
||||
hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port)
|
||||
}
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "tcp":
|
||||
listener, err = t.listen(hostport, nil)
|
||||
case "tls":
|
||||
listener, err = t.listen(hostport, t.tls.forListener)
|
||||
default:
|
||||
t.links.core.log.Errorln("Failed to add listener: listener", u.String(), "is not correctly formatted, ignoring")
|
||||
}
|
||||
return listener, err
|
||||
}
|
||||
|
||||
func (t *tcp) listen(listenaddr string, upgrade *TcpUpgrade) (*TcpListener, error) {
|
||||
var err error
|
||||
|
||||
ctx := t.links.core.ctx
|
||||
lc := net.ListenConfig{
|
||||
Control: t.tcpContext,
|
||||
}
|
||||
listener, err := lc.Listen(ctx, "tcp", listenaddr)
|
||||
if err == nil {
|
||||
l := TcpListener{
|
||||
Listener: listener,
|
||||
opts: tcpOptions{upgrade: upgrade},
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
t.waitgroup.Add(1)
|
||||
go t.listener(&l, listenaddr)
|
||||
return &l, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Runs the listener, which spawns off goroutines for incoming connections.
|
||||
func (t *tcp) listener(l *TcpListener, listenaddr string) {
|
||||
defer t.waitgroup.Done()
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
// Track the listener so that we can find it again in future
|
||||
t.mutex.Lock()
|
||||
if _, isIn := t.listeners[listenaddr]; isIn {
|
||||
t.mutex.Unlock()
|
||||
l.Listener.Close()
|
||||
return
|
||||
}
|
||||
callproto := "TCP"
|
||||
if l.opts.upgrade != nil {
|
||||
callproto = strings.ToUpper(l.opts.upgrade.name)
|
||||
}
|
||||
t.listeners[listenaddr] = l
|
||||
t.mutex.Unlock()
|
||||
// And here we go!
|
||||
defer func() {
|
||||
t.links.core.log.Infoln("Stopping", callproto, "listener on:", l.Listener.Addr().String())
|
||||
l.Listener.Close()
|
||||
t.mutex.Lock()
|
||||
delete(t.listeners, listenaddr)
|
||||
t.mutex.Unlock()
|
||||
}()
|
||||
t.links.core.log.Infoln("Listening for", callproto, "on:", l.Listener.Addr().String())
|
||||
go func() {
|
||||
<-l.stop
|
||||
l.Listener.Close()
|
||||
}()
|
||||
defer l.Stop()
|
||||
for {
|
||||
sock, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
t.links.core.log.Errorln("Failed to accept connection:", err)
|
||||
select {
|
||||
case <-l.stop:
|
||||
return
|
||||
default:
|
||||
}
|
||||
time.Sleep(time.Second) // So we don't busy loop
|
||||
continue
|
||||
}
|
||||
t.waitgroup.Add(1)
|
||||
options := l.opts
|
||||
go t.handler(sock, true, options)
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if we already are calling this address
|
||||
func (t *tcp) startCalling(saddr string) bool {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
_, isIn := t.calls[saddr]
|
||||
t.calls[saddr] = struct{}{}
|
||||
return !isIn
|
||||
}
|
||||
|
||||
// Checks if a connection already exists.
|
||||
// If not, it adds it to the list of active outgoing calls (to block future attempts) and dials the address.
|
||||
// If the dial is successful, it launches the handler.
|
||||
// When finished, it removes the outgoing call, so reconnection attempts can be made later.
|
||||
// This all happens in a separate goroutine that it spawns.
|
||||
func (t *tcp) call(saddr string, options tcpOptions, sintf string) {
|
||||
go func() {
|
||||
callname := saddr
|
||||
callproto := "TCP"
|
||||
if options.upgrade != nil {
|
||||
callproto = strings.ToUpper(options.upgrade.name)
|
||||
}
|
||||
if sintf != "" {
|
||||
callname = fmt.Sprintf("%s/%s/%s", callproto, saddr, sintf)
|
||||
}
|
||||
if !t.startCalling(callname) {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
// Block new calls for a little while, to mitigate livelock scenarios
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
delay := default_timeout + time.Duration(rand.Intn(10000))*time.Millisecond
|
||||
time.Sleep(delay)
|
||||
t.mutex.Lock()
|
||||
delete(t.calls, callname)
|
||||
t.mutex.Unlock()
|
||||
}()
|
||||
var conn net.Conn
|
||||
var err error
|
||||
if options.socksProxyAddr != "" {
|
||||
if sintf != "" {
|
||||
return
|
||||
}
|
||||
dialerdst, er := net.ResolveTCPAddr("tcp", options.socksProxyAddr)
|
||||
if er != nil {
|
||||
return
|
||||
}
|
||||
var dialer proxy.Dialer
|
||||
dialer, err = proxy.SOCKS5("tcp", dialerdst.String(), options.socksProxyAuth, proxy.Direct)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ctx, done := context.WithTimeout(t.links.core.ctx, default_timeout)
|
||||
conn, err = dialer.(proxy.ContextDialer).DialContext(ctx, "tcp", saddr)
|
||||
done()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.waitgroup.Add(1)
|
||||
options.socksPeerAddr = saddr
|
||||
if ch := t.handler(conn, false, options); ch != nil {
|
||||
<-ch
|
||||
}
|
||||
} else {
|
||||
dst, err := net.ResolveTCPAddr("tcp", saddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if dst.IP.IsLinkLocalUnicast() {
|
||||
dst.Zone = sintf
|
||||
if dst.Zone == "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
dialer := net.Dialer{
|
||||
Control: t.tcpContext,
|
||||
}
|
||||
if sintf != "" {
|
||||
dialer.Control = t.getControl(sintf)
|
||||
ief, err := net.InterfaceByName(sintf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if ief.Flags&net.FlagUp == 0 {
|
||||
return
|
||||
}
|
||||
addrs, err := ief.Addrs()
|
||||
if err == nil {
|
||||
for addrindex, addr := range addrs {
|
||||
src, _, err := net.ParseCIDR(addr.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if src.Equal(dst.IP) {
|
||||
continue
|
||||
}
|
||||
if !src.IsGlobalUnicast() && !src.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
bothglobal := src.IsGlobalUnicast() == dst.IP.IsGlobalUnicast()
|
||||
bothlinklocal := src.IsLinkLocalUnicast() == dst.IP.IsLinkLocalUnicast()
|
||||
if !bothglobal && !bothlinklocal {
|
||||
continue
|
||||
}
|
||||
if (src.To4() != nil) != (dst.IP.To4() != nil) {
|
||||
continue
|
||||
}
|
||||
if bothglobal || bothlinklocal || addrindex == len(addrs)-1 {
|
||||
dialer.LocalAddr = &net.TCPAddr{
|
||||
IP: src,
|
||||
Port: 0,
|
||||
Zone: sintf,
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if dialer.LocalAddr == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx, done := context.WithTimeout(t.links.core.ctx, default_timeout)
|
||||
conn, err = dialer.DialContext(ctx, "tcp", dst.String())
|
||||
done()
|
||||
if err != nil {
|
||||
t.links.core.log.Debugf("Failed to dial %s: %s", callproto, err)
|
||||
return
|
||||
}
|
||||
t.waitgroup.Add(1)
|
||||
if ch := t.handler(conn, false, options); ch != nil {
|
||||
<-ch
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (t *tcp) handler(sock net.Conn, incoming bool, options tcpOptions) chan struct{} {
|
||||
defer t.waitgroup.Done() // Happens after sock.close
|
||||
defer sock.Close()
|
||||
t.setExtraOptions(sock)
|
||||
var upgraded bool
|
||||
if options.upgrade != nil {
|
||||
var err error
|
||||
if sock, err = options.upgrade.upgrade(sock, &options); err != nil {
|
||||
t.links.core.log.Errorln("TCP handler upgrade failed:", err)
|
||||
return nil
|
||||
}
|
||||
upgraded = true
|
||||
}
|
||||
var name, proto, local, remote string
|
||||
if options.socksProxyAddr != "" {
|
||||
name = "socks://" + sock.RemoteAddr().String() + "/" + options.socksPeerAddr
|
||||
proto = "socks"
|
||||
local, _, _ = net.SplitHostPort(sock.LocalAddr().String())
|
||||
remote, _, _ = net.SplitHostPort(options.socksPeerAddr)
|
||||
} else {
|
||||
if upgraded {
|
||||
proto = options.upgrade.name
|
||||
name = proto + "://" + sock.RemoteAddr().String()
|
||||
} else {
|
||||
proto = "tcp"
|
||||
name = proto + "://" + sock.RemoteAddr().String()
|
||||
}
|
||||
local, _, _ = net.SplitHostPort(sock.LocalAddr().String())
|
||||
remote, _, _ = net.SplitHostPort(sock.RemoteAddr().String())
|
||||
}
|
||||
localIP := net.ParseIP(local)
|
||||
if localIP = localIP.To16(); localIP != nil {
|
||||
var laddr address.Address
|
||||
var lsubnet address.Subnet
|
||||
copy(laddr[:], localIP)
|
||||
copy(lsubnet[:], localIP)
|
||||
if laddr.IsValid() || lsubnet.IsValid() {
|
||||
// The local address is with the network address/prefix range
|
||||
// This would route ygg over ygg, which we don't want
|
||||
// FIXME ideally this check should happen outside of the core library
|
||||
// Maybe dial/listen at the application level
|
||||
// Then pass a net.Conn to the core library (after these kinds of checks are done)
|
||||
t.links.core.log.Debugln("Dropping ygg-tunneled connection", local, remote)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
force := net.ParseIP(strings.Split(remote, "%")[0]).IsLinkLocalUnicast()
|
||||
link, err := t.links.create(sock, name, proto, local, remote, incoming, force, options.linkOptions)
|
||||
if err != nil {
|
||||
t.links.core.log.Println(err)
|
||||
panic(err)
|
||||
}
|
||||
t.links.core.log.Debugln("DEBUG: starting handler for", name)
|
||||
ch, err := link.handler()
|
||||
t.links.core.log.Debugln("DEBUG: stopped handler for", name, err)
|
||||
return ch
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
|
||||
|
||||
func (t *tcp) 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
|
||||
}
|
||||
|
||||
func (t *tcp) getControl(sintf string) func(string, string, syscall.RawConn) error {
|
||||
return func(network, address string, c syscall.RawConn) error {
|
||||
var err error
|
||||
btd := func(fd uintptr) {
|
||||
err = unix.BindToDevice(int(fd), sintf)
|
||||
}
|
||||
_ = c.Control(btd)
|
||||
if err != nil {
|
||||
t.links.core.log.Debugln("Failed to set SO_BINDTODEVICE:", sintf)
|
||||
}
|
||||
return t.tcpContext(network, address, c)
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// +build !darwin,!linux
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
|
||||
|
||||
func (t *tcp) tcpContext(network, address string, c syscall.RawConn) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tcp) getControl(sintf string) func(string, string, syscall.RawConn) error {
|
||||
return t.tcpContext
|
||||
}
|
126
src/core/tls.go
126
src/core/tls.go
|
@ -1,125 +1,29 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"log"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type tcptls struct {
|
||||
tcp *tcp
|
||||
config *tls.Config
|
||||
forDialer *TcpUpgrade
|
||||
forListener *TcpUpgrade
|
||||
}
|
||||
|
||||
func (t *tcptls) init(tcp *tcp) {
|
||||
t.tcp = tcp
|
||||
t.forDialer = &TcpUpgrade{
|
||||
upgrade: t.upgradeDialer,
|
||||
name: "tls",
|
||||
}
|
||||
t.forListener = &TcpUpgrade{
|
||||
upgrade: t.upgradeListener,
|
||||
name: "tls",
|
||||
}
|
||||
|
||||
edpriv := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
|
||||
copy(edpriv[:], tcp.links.core.secret[:])
|
||||
|
||||
certBuf := &bytes.Buffer{}
|
||||
|
||||
// TODO: because NotAfter is finite, we should add some mechanism to regenerate the certificate and restart the listeners periodically for nodes with very high uptimes. Perhaps regenerate certs and restart listeners every few months or so.
|
||||
pubtemp := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
CommonName: hex.EncodeToString(tcp.links.core.public[:]),
|
||||
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
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
derbytes, err := x509.CreateCertificate(rand.Reader, &pubtemp, &pubtemp, edpriv.Public(), edpriv)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create certificate: %s", err)
|
||||
}
|
||||
|
||||
if err := pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derbytes}); err != nil {
|
||||
panic("failed to encode certificate into PEM")
|
||||
}
|
||||
|
||||
cpool := x509.NewCertPool()
|
||||
cpool.AppendCertsFromPEM(derbytes)
|
||||
|
||||
t.config = &tls.Config{
|
||||
RootCAs: cpool,
|
||||
Certificates: []tls.Certificate{
|
||||
{
|
||||
Certificate: [][]byte{derbytes},
|
||||
PrivateKey: edpriv,
|
||||
},
|
||||
},
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
VerifyPeerCertificate: c.verifyTLSCertificate,
|
||||
VerifyConnection: c.verifyTLSConnection,
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (t *tcptls) configForOptions(options *tcpOptions) *tls.Config {
|
||||
config := *t.config
|
||||
config.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||
if len(rawCerts) != 1 {
|
||||
return errors.New("tls not exactly 1 cert")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(rawCerts[0])
|
||||
if err != nil {
|
||||
return errors.New("tls failed to parse cert")
|
||||
}
|
||||
if cert.PublicKeyAlgorithm != x509.Ed25519 {
|
||||
return errors.New("tls wrong cert algorithm")
|
||||
}
|
||||
pk := cert.PublicKey.(ed25519.PublicKey)
|
||||
var key keyArray
|
||||
copy(key[:], pk)
|
||||
// If options does not have a pinned key, then pin one now
|
||||
if options.pinnedEd25519Keys == nil {
|
||||
options.pinnedEd25519Keys = make(map[keyArray]struct{})
|
||||
options.pinnedEd25519Keys[key] = struct{}{}
|
||||
}
|
||||
if _, isIn := options.pinnedEd25519Keys[key]; !isIn {
|
||||
return errors.New("tls key does not match pinned key")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return &config
|
||||
func (c *Core) verifyTLSCertificate(_ [][]byte, _ [][]*x509.Certificate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tcptls) upgradeListener(c net.Conn, options *tcpOptions) (net.Conn, error) {
|
||||
config := t.configForOptions(options)
|
||||
conn := tls.Server(c, config)
|
||||
if err := conn.Handshake(); err != nil {
|
||||
return c, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (t *tcptls) upgradeDialer(c net.Conn, options *tcpOptions) (net.Conn, error) {
|
||||
config := t.configForOptions(options)
|
||||
conn := tls.Client(c, config)
|
||||
if err := conn.Handshake(); err != nil {
|
||||
return c, err
|
||||
}
|
||||
return conn, nil
|
||||
func (c *Core) verifyTLSConnection(_ tls.ConnectionState) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
package core
|
||||
|
||||
// Out-of-band packet types
|
||||
const (
|
||||
typeKeyDummy = iota // nolint:deadcode,varcheck
|
||||
typeKeyLookup
|
||||
typeKeyResponse
|
||||
)
|
||||
|
||||
// In-band packet types
|
||||
const (
|
||||
typeSessionDummy = iota // nolint:deadcode,varcheck
|
||||
|
|
|
@ -4,65 +4,166 @@ package core
|
|||
// Used in the initial connection setup and key exchange
|
||||
// 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.
|
||||
// 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.
|
||||
type version_metadata struct {
|
||||
meta [4]byte
|
||||
ver uint8 // 1 byte in this version
|
||||
// Everything after this point potentially depends on the version number, and is subject to change in future versions
|
||||
minorVer uint8 // 1 byte in this version
|
||||
key ed25519.PublicKey
|
||||
majorVer uint16
|
||||
minorVer uint16
|
||||
publicKey ed25519.PublicKey
|
||||
priority uint8
|
||||
}
|
||||
|
||||
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.
|
||||
func version_getBaseMetadata() version_metadata {
|
||||
return version_metadata{
|
||||
meta: [4]byte{'m', 'e', 't', 'a'},
|
||||
ver: 0,
|
||||
minorVer: 4,
|
||||
majorVer: ProtocolVersionMajor,
|
||||
minorVer: ProtocolVersionMinor,
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (m *version_metadata) encode() []byte {
|
||||
bs := make([]byte, 0, version_getMetaLength())
|
||||
bs = append(bs, m.meta[:]...)
|
||||
bs = append(bs, m.ver)
|
||||
bs = append(bs, m.minorVer)
|
||||
bs = append(bs, m.key[:]...)
|
||||
if len(bs) != version_getMetaLength() {
|
||||
panic("Inconsistent metadata length")
|
||||
func (m *version_metadata) encode(privateKey ed25519.PrivateKey, password []byte) ([]byte, error) {
|
||||
bs := make([]byte, 0, 64)
|
||||
bs = append(bs, 'm', 'e', 't', 'a')
|
||||
bs = append(bs, 0, 0) // Remaining message length
|
||||
|
||||
bs = binary.BigEndian.AppendUint16(bs, metaVersionMajor)
|
||||
bs = binary.BigEndian.AppendUint16(bs, 2)
|
||||
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.
|
||||
func (m *version_metadata) decode(bs []byte) bool {
|
||||
if len(bs) != version_getMetaLength() {
|
||||
return false
|
||||
func (m *version_metadata) decode(r io.Reader, password []byte) error {
|
||||
bh := [6]byte{}
|
||||
if _, err := io.ReadFull(r, bh[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
offset := 0
|
||||
offset += copy(m.meta[:], bs[offset:])
|
||||
m.ver, offset = bs[offset], offset+1
|
||||
m.minorVer, offset = bs[offset], offset+1
|
||||
m.key = append([]byte(nil), bs[offset:]...)
|
||||
return true
|
||||
meta := [4]byte{'m', 'e', 't', 'a'}
|
||||
if !bytes.Equal(bh[:4], meta[:]) {
|
||||
return ErrHandshakeInvalidPreamble
|
||||
}
|
||||
hl := binary.BigEndian.Uint16(bh[4:6])
|
||||
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.
|
||||
func (m *version_metadata) check() bool {
|
||||
base := version_getBaseMetadata()
|
||||
return base.meta == m.meta && base.ver == m.ver && base.minorVer == m.minorVer
|
||||
switch {
|
||||
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,44 +0,0 @@
|
|||
package defaults
|
||||
|
||||
import "github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
|
||||
type MulticastInterfaceConfig = config.MulticastInterfaceConfig
|
||||
|
||||
// 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/TAP
|
||||
MaximumIfMTU uint64
|
||||
DefaultIfMTU uint64
|
||||
DefaultIfName string
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Create a node configuration and populate it.
|
||||
cfg := new(config.NodeConfig)
|
||||
cfg.NewKeys()
|
||||
cfg.Listen = []string{}
|
||||
cfg.AdminListen = GetDefaults().DefaultAdminListen
|
||||
cfg.Peers = []string{}
|
||||
cfg.InterfacePeers = map[string][]string{}
|
||||
cfg.AllowedPublicKeys = []string{}
|
||||
cfg.MulticastInterfaces = GetDefaults().DefaultMulticastInterfaces
|
||||
cfg.IfName = GetDefaults().DefaultIfName
|
||||
cfg.IfMTU = GetDefaults().DefaultIfMTU
|
||||
cfg.NodeInfoPrivacy = false
|
||||
|
||||
return cfg
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package core
|
||||
package ipv6rwc
|
||||
|
||||
// The ICMPv6 module implements functions to easily create ICMPv6
|
||||
// packets. These functions, when mixed with the built-in Go IPv6
|
|
@ -1,4 +1,4 @@
|
|||
package core
|
||||
package ipv6rwc
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
|
@ -14,14 +14,24 @@ import (
|
|||
iwt "github.com/Arceliar/ironwood/types"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/address"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||
)
|
||||
|
||||
const keyStoreTimeout = 2 * time.Minute
|
||||
|
||||
/*
|
||||
// Out-of-band packet types
|
||||
const (
|
||||
typeKeyDummy = iota // nolint:deadcode,varcheck
|
||||
typeKeyLookup
|
||||
typeKeyResponse
|
||||
)
|
||||
*/
|
||||
|
||||
type keyArray [ed25519.PublicKeySize]byte
|
||||
|
||||
type keyStore struct {
|
||||
core *Core
|
||||
core *core.Core
|
||||
address address.Address
|
||||
subnet address.Subnet
|
||||
mutex sync.Mutex
|
||||
|
@ -45,14 +55,17 @@ type buffer struct {
|
|||
timeout *time.Timer
|
||||
}
|
||||
|
||||
func (k *keyStore) init(core *Core) {
|
||||
k.core = core
|
||||
k.address = *address.AddrForKey(k.core.public)
|
||||
k.subnet = *address.SubnetForKey(k.core.public)
|
||||
if err := k.core.pc.SetOutOfBandHandler(k.oobHandler); err != nil {
|
||||
func (k *keyStore) init(c *core.Core) {
|
||||
k.core = c
|
||||
k.address = *address.AddrForKey(k.core.PublicKey())
|
||||
k.subnet = *address.SubnetForKey(k.core.PublicKey())
|
||||
/*if err := k.core.SetOutOfBandHandler(k.oobHandler); err != nil {
|
||||
err = fmt.Errorf("tun.core.SetOutOfBandHander: %w", err)
|
||||
panic(err)
|
||||
}
|
||||
}*/
|
||||
k.core.SetPathNotify(func(key ed25519.PublicKey) {
|
||||
k.update(key)
|
||||
})
|
||||
k.keyToInfo = make(map[keyArray]*keyInfo)
|
||||
k.addrToInfo = make(map[address.Address]*keyInfo)
|
||||
k.addrBuffer = make(map[address.Address]*buffer)
|
||||
|
@ -66,7 +79,7 @@ func (k *keyStore) sendToAddress(addr address.Address, bs []byte) {
|
|||
if info := k.addrToInfo[addr]; info != nil {
|
||||
k.resetTimeout(info)
|
||||
k.mutex.Unlock()
|
||||
_, _ = k.core.pc.WriteTo(bs, iwt.Addr(info.key[:]))
|
||||
_, _ = k.core.WriteTo(bs, iwt.Addr(info.key[:]))
|
||||
} else {
|
||||
var buf *buffer
|
||||
if buf = k.addrBuffer[addr]; buf == nil {
|
||||
|
@ -95,7 +108,7 @@ func (k *keyStore) sendToSubnet(subnet address.Subnet, bs []byte) {
|
|||
if info := k.subnetToInfo[subnet]; info != nil {
|
||||
k.resetTimeout(info)
|
||||
k.mutex.Unlock()
|
||||
_, _ = k.core.pc.WriteTo(bs, iwt.Addr(info.key[:]))
|
||||
_, _ = k.core.WriteTo(bs, iwt.Addr(info.key[:]))
|
||||
} else {
|
||||
var buf *buffer
|
||||
if buf = k.subnetBuffer[subnet]; buf == nil {
|
||||
|
@ -124,6 +137,7 @@ func (k *keyStore) update(key ed25519.PublicKey) *keyInfo {
|
|||
var kArray keyArray
|
||||
copy(kArray[:], key)
|
||||
var info *keyInfo
|
||||
var packets [][]byte
|
||||
if info = k.keyToInfo[kArray]; info == nil {
|
||||
info = new(keyInfo)
|
||||
info.key = kArray
|
||||
|
@ -132,19 +146,19 @@ func (k *keyStore) update(key ed25519.PublicKey) *keyInfo {
|
|||
k.keyToInfo[info.key] = info
|
||||
k.addrToInfo[info.address] = info
|
||||
k.subnetToInfo[info.subnet] = info
|
||||
k.resetTimeout(info)
|
||||
k.mutex.Unlock()
|
||||
if buf := k.addrBuffer[info.address]; buf != nil {
|
||||
k.core.pc.WriteTo(buf.packet, iwt.Addr(info.key[:]))
|
||||
packets = append(packets, buf.packet)
|
||||
delete(k.addrBuffer, info.address)
|
||||
}
|
||||
if buf := k.subnetBuffer[info.subnet]; buf != nil {
|
||||
k.core.pc.WriteTo(buf.packet, iwt.Addr(info.key[:]))
|
||||
packets = append(packets, buf.packet)
|
||||
delete(k.subnetBuffer, info.subnet)
|
||||
}
|
||||
} else {
|
||||
k.resetTimeout(info)
|
||||
k.mutex.Unlock()
|
||||
}
|
||||
k.resetTimeout(info)
|
||||
k.mutex.Unlock()
|
||||
for _, packet := range packets {
|
||||
_, _ = k.core.WriteTo(packet, iwt.Addr(info.key[:]))
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
@ -168,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 {
|
||||
return
|
||||
}
|
||||
|
@ -189,48 +204,39 @@ func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) {
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func (k *keyStore) sendKeyLookup(partial ed25519.PublicKey) {
|
||||
sig := ed25519.Sign(k.core.secret, partial[:])
|
||||
bs := append([]byte{typeKeyLookup}, sig...)
|
||||
_ = k.core.pc.SendOutOfBand(partial, bs)
|
||||
/*
|
||||
sig := ed25519.Sign(k.core.PrivateKey(), partial[:])
|
||||
bs := append([]byte{typeKeyLookup}, sig...)
|
||||
//_ = k.core.SendOutOfBand(partial, bs)
|
||||
_ = bs
|
||||
*/
|
||||
k.core.SendLookup(partial)
|
||||
}
|
||||
|
||||
func (k *keyStore) sendKeyResponse(dest ed25519.PublicKey) {
|
||||
sig := ed25519.Sign(k.core.secret, dest[:])
|
||||
/*
|
||||
func (k *keyStore) sendKeyResponse(dest ed25519.PublicKey) { // nolint:unused
|
||||
sig := ed25519.Sign(k.core.PrivateKey(), dest[:])
|
||||
bs := append([]byte{typeKeyResponse}, sig...)
|
||||
_ = k.core.pc.SendOutOfBand(dest, bs)
|
||||
}
|
||||
|
||||
func (k *keyStore) maxSessionMTU() uint64 {
|
||||
const sessionTypeOverhead = 1
|
||||
return k.core.pc.MTU() - sessionTypeOverhead
|
||||
//_ = k.core.SendOutOfBand(dest, bs)
|
||||
_ = bs
|
||||
}
|
||||
*/
|
||||
|
||||
func (k *keyStore) readPC(p []byte) (int, error) {
|
||||
buf := make([]byte, k.core.pc.MTU(), 65535)
|
||||
buf := make([]byte, k.core.MTU(), 65535)
|
||||
for {
|
||||
bs := buf
|
||||
n, from, err := k.core.pc.ReadFrom(bs)
|
||||
n, from, err := k.core.ReadFrom(bs)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
switch bs[0] {
|
||||
case typeSessionTraffic:
|
||||
// This is what we want to handle here
|
||||
case typeSessionProto:
|
||||
var key keyArray
|
||||
copy(key[:], from.(iwt.Addr))
|
||||
data := append([]byte(nil), bs[1:n]...)
|
||||
k.core.proto.handleProto(nil, key, data)
|
||||
continue
|
||||
default:
|
||||
continue
|
||||
}
|
||||
bs = bs[1:n]
|
||||
bs = bs[:n]
|
||||
if len(bs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
@ -245,11 +251,11 @@ func (k *keyStore) readPC(p []byte) (int, error) {
|
|||
k.mutex.Unlock()
|
||||
if len(bs) > mtu {
|
||||
// Using bs would make it leak off the stack, so copy to buf
|
||||
buf := make([]byte, 40)
|
||||
copy(buf, bs)
|
||||
buf := make([]byte, 512)
|
||||
cn := copy(buf, bs)
|
||||
ptb := &icmp.PacketTooBig{
|
||||
MTU: mtu,
|
||||
Data: buf[:40],
|
||||
Data: buf[:cn],
|
||||
}
|
||||
if packet, err := CreateICMPv6(buf[8:24], buf[24:40], ipv6.ICMPTypePacketTooBig, 0, ptb); err == nil {
|
||||
_, _ = k.writePC(packet)
|
||||
|
@ -294,15 +300,69 @@ func (k *keyStore) writePC(bs []byte) (int, error) {
|
|||
strErr := fmt.Sprint("incorrect source address: ", net.IP(srcAddr[:]).String())
|
||||
return 0, errors.New(strErr)
|
||||
}
|
||||
buf := make([]byte, 1+len(bs), 65535)
|
||||
buf[0] = typeSessionTraffic
|
||||
copy(buf[1:], bs)
|
||||
if dstAddr.IsValid() {
|
||||
k.sendToAddress(dstAddr, buf)
|
||||
k.sendToAddress(dstAddr, bs)
|
||||
} else if dstSubnet.IsValid() {
|
||||
k.sendToSubnet(dstSubnet, buf)
|
||||
k.sendToSubnet(dstSubnet, bs)
|
||||
} else {
|
||||
return 0, errors.New("invalid destination address")
|
||||
}
|
||||
return len(bs), nil
|
||||
}
|
||||
|
||||
// Exported API
|
||||
|
||||
func (k *keyStore) MaxMTU() uint64 {
|
||||
return k.core.MTU()
|
||||
}
|
||||
|
||||
func (k *keyStore) SetMTU(mtu uint64) {
|
||||
if mtu > k.MaxMTU() {
|
||||
mtu = k.MaxMTU()
|
||||
}
|
||||
if mtu < 1280 {
|
||||
mtu = 1280
|
||||
}
|
||||
k.mutex.Lock()
|
||||
k.mtu = mtu
|
||||
k.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (k *keyStore) MTU() uint64 {
|
||||
k.mutex.Lock()
|
||||
mtu := k.mtu
|
||||
k.mutex.Unlock()
|
||||
return mtu
|
||||
}
|
||||
|
||||
type ReadWriteCloser struct {
|
||||
keyStore
|
||||
}
|
||||
|
||||
func NewReadWriteCloser(c *core.Core) *ReadWriteCloser {
|
||||
rwc := new(ReadWriteCloser)
|
||||
rwc.init(c)
|
||||
return rwc
|
||||
}
|
||||
|
||||
func (rwc *ReadWriteCloser) Address() address.Address {
|
||||
return rwc.address
|
||||
}
|
||||
|
||||
func (rwc *ReadWriteCloser) Subnet() address.Subnet {
|
||||
return rwc.subnet
|
||||
}
|
||||
|
||||
func (rwc *ReadWriteCloser) Read(p []byte) (n int, err error) {
|
||||
return rwc.readPC(p)
|
||||
}
|
||||
|
||||
func (rwc *ReadWriteCloser) Write(p []byte) (n int, err error) {
|
||||
return rwc.writePC(p)
|
||||
}
|
||||
|
||||
func (rwc *ReadWriteCloser) Close() error {
|
||||
err := rwc.core.Close()
|
||||
rwc.core.Stop()
|
||||
return err
|
||||
}
|
|
@ -2,33 +2,63 @@ package multicast
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/Arceliar/phony"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||
)
|
||||
|
||||
type GetMulticastInterfacesRequest struct{}
|
||||
type GetMulticastInterfacesResponse struct {
|
||||
Interfaces []string `json:"multicast_interfaces"`
|
||||
Interfaces []MulticastInterfaceState `json:"multicast_interfaces"`
|
||||
}
|
||||
|
||||
func (m *Multicast) getMulticastInterfacesHandler(req *GetMulticastInterfacesRequest, res *GetMulticastInterfacesResponse) error {
|
||||
res.Interfaces = []string{}
|
||||
for _, v := range m.Interfaces() {
|
||||
res.Interfaces = append(res.Interfaces, v.Name)
|
||||
}
|
||||
type MulticastInterfaceState struct {
|
||||
Name string `json:"name"`
|
||||
Address string `json:"address"`
|
||||
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
|
||||
}
|
||||
|
||||
func (m *Multicast) SetupAdminHandlers(a *admin.AdminSocket) {
|
||||
_ = a.AddHandler("getMulticastInterfaces", []string{}, func(in json.RawMessage) (interface{}, error) {
|
||||
req := &GetMulticastInterfacesRequest{}
|
||||
res := &GetMulticastInterfacesResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := m.getMulticastInterfacesHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
})
|
||||
_ = a.AddHandler(
|
||||
"getMulticastInterfaces", "Show which interfaces multicast is enabled on", []string{},
|
||||
func(in json.RawMessage) (interface{}, error) {
|
||||
req := &GetMulticastInterfacesRequest{}
|
||||
res := &GetMulticastInterfacesResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := m.getMulticastInterfacesHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, 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,19 +4,20 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/Arceliar/phony"
|
||||
"github.com/gologme/log"
|
||||
"github.com/wlynxg/anet"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/config"
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/core"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
|
@ -27,65 +28,75 @@ import (
|
|||
type Multicast struct {
|
||||
phony.Inbox
|
||||
core *core.Core
|
||||
config *config.NodeConfig
|
||||
log *log.Logger
|
||||
sock *ipv6.PacketConn
|
||||
groupAddr string
|
||||
listeners map[string]*listenerInfo
|
||||
isOpen bool
|
||||
_interfaces map[string]interfaceInfo
|
||||
running atomic.Bool
|
||||
_listeners map[string]*listenerInfo
|
||||
_interfaces map[string]*interfaceInfo
|
||||
_timer *time.Timer
|
||||
config struct {
|
||||
_groupAddr GroupAddress
|
||||
_interfaces map[MulticastInterface]struct{}
|
||||
}
|
||||
}
|
||||
|
||||
type interfaceInfo struct {
|
||||
iface net.Interface
|
||||
addrs []net.Addr
|
||||
beacon bool
|
||||
listen bool
|
||||
port uint16
|
||||
iface net.Interface
|
||||
addrs []net.Addr
|
||||
beacon bool
|
||||
listen bool
|
||||
port uint16
|
||||
priority uint8
|
||||
password []byte
|
||||
hash []byte
|
||||
}
|
||||
|
||||
type listenerInfo struct {
|
||||
listener *core.TcpListener
|
||||
listener *core.Listener
|
||||
time time.Time
|
||||
interval time.Duration
|
||||
port uint16
|
||||
}
|
||||
|
||||
// Init prepares the multicast interface for use.
|
||||
func (m *Multicast) Init(core *core.Core, nc *config.NodeConfig, log *log.Logger, options interface{}) error {
|
||||
m.core = core
|
||||
m.config = nc
|
||||
m.log = log
|
||||
m.listeners = make(map[string]*listenerInfo)
|
||||
m._interfaces = make(map[string]interfaceInfo)
|
||||
m.groupAddr = "[ff02::114]:9001"
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts the multicast interface. This launches goroutines which will
|
||||
// listen for multicast beacons from other hosts and will advertise multicast
|
||||
// beacons out to the network.
|
||||
func (m *Multicast) Start() error {
|
||||
func New(core *core.Core, log *log.Logger, opts ...SetupOption) (*Multicast, error) {
|
||||
m := &Multicast{
|
||||
core: core,
|
||||
log: log,
|
||||
_listeners: make(map[string]*listenerInfo),
|
||||
_interfaces: make(map[string]*interfaceInfo),
|
||||
}
|
||||
m.config._interfaces = map[MulticastInterface]struct{}{}
|
||||
m.config._groupAddr = GroupAddress("[ff02::114]:9001")
|
||||
for _, opt := range opts {
|
||||
m._applyOption(opt)
|
||||
}
|
||||
var err error
|
||||
phony.Block(m, func() {
|
||||
err = m._start()
|
||||
})
|
||||
m.log.Debugln("Started multicast module")
|
||||
return err
|
||||
return m, err
|
||||
}
|
||||
|
||||
func (m *Multicast) _start() error {
|
||||
if m.isOpen {
|
||||
if !m.running.CompareAndSwap(false, true) {
|
||||
return fmt.Errorf("multicast module is already started")
|
||||
}
|
||||
m.config.RLock()
|
||||
defer m.config.RUnlock()
|
||||
if len(m.config.MulticastInterfaces) == 0 {
|
||||
var anyEnabled bool
|
||||
for intf := range m.config._interfaces {
|
||||
anyEnabled = anyEnabled || intf.Beacon || intf.Listen
|
||||
}
|
||||
if !anyEnabled {
|
||||
m.running.Store(false)
|
||||
return nil
|
||||
}
|
||||
m.log.Infoln("Starting multicast module")
|
||||
addr, err := net.ResolveUDPAddr("udp", m.groupAddr)
|
||||
m.log.Debugln("Starting multicast module")
|
||||
defer m.log.Debugln("Started multicast module")
|
||||
addr, err := net.ResolveUDPAddr("udp", string(m.config._groupAddr))
|
||||
if err != nil {
|
||||
m.running.Store(false)
|
||||
return err
|
||||
}
|
||||
listenString := fmt.Sprintf("[::]:%v", addr.Port)
|
||||
|
@ -94,6 +105,7 @@ func (m *Multicast) _start() error {
|
|||
}
|
||||
conn, err := lc.ListenPacket(context.Background(), "udp6", listenString)
|
||||
if err != nil {
|
||||
m.running.Store(false)
|
||||
return err
|
||||
}
|
||||
m.sock = ipv6.NewPacketConn(conn)
|
||||
|
@ -101,7 +113,6 @@ func (m *Multicast) _start() error {
|
|||
// Windows can't set this flag, so we need to handle it in other ways
|
||||
}
|
||||
|
||||
m.isOpen = true
|
||||
go m.listen()
|
||||
m.Act(nil, m._multicastStarted)
|
||||
m.Act(nil, m._announce)
|
||||
|
@ -111,11 +122,7 @@ func (m *Multicast) _start() error {
|
|||
|
||||
// IsStarted returns true if the module has been started.
|
||||
func (m *Multicast) IsStarted() bool {
|
||||
var isOpen bool
|
||||
phony.Block(m, func() {
|
||||
isOpen = m.isOpen
|
||||
})
|
||||
return isOpen
|
||||
return m.running.Load()
|
||||
}
|
||||
|
||||
// Stop stops the multicast module.
|
||||
|
@ -129,8 +136,10 @@ 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.isOpen = false
|
||||
if m.sock != nil {
|
||||
m.sock.Close()
|
||||
}
|
||||
|
@ -138,16 +147,24 @@ func (m *Multicast) _stop() error {
|
|||
}
|
||||
|
||||
func (m *Multicast) _updateInterfaces() {
|
||||
interfaces := m.getAllowedInterfaces()
|
||||
interfaces := m._getAllowedInterfaces()
|
||||
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 {
|
||||
m.log.Warnf("Failed up get addresses for interface %s: %s", name, err)
|
||||
delete(interfaces, name)
|
||||
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
|
||||
m.log.Debugf("Discovered addresses for interface %s: %s", name, addrs)
|
||||
}
|
||||
m._interfaces = interfaces
|
||||
}
|
||||
|
@ -163,75 +180,91 @@ func (m *Multicast) Interfaces() map[string]net.Interface {
|
|||
}
|
||||
|
||||
// getAllowedInterfaces returns the currently known/enabled multicast interfaces.
|
||||
func (m *Multicast) getAllowedInterfaces() map[string]interfaceInfo {
|
||||
interfaces := make(map[string]interfaceInfo)
|
||||
// Get interface expressions from config
|
||||
ifcfgs := m.config.MulticastInterfaces
|
||||
func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo {
|
||||
interfaces := make(map[string]*interfaceInfo)
|
||||
// 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 {
|
||||
// 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
|
||||
}
|
||||
// Work out which interfaces to announce on
|
||||
pk := m.core.PublicKey()
|
||||
for _, iface := range allifaces {
|
||||
if iface.Flags&net.FlagUp == 0 {
|
||||
// Ignore interfaces that are down
|
||||
continue
|
||||
switch {
|
||||
case iface.Flags&net.FlagUp == 0:
|
||||
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:
|
||||
continue // Ignore non-multicast interfaces
|
||||
case iface.Flags&net.FlagPointToPoint != 0:
|
||||
continue // Ignore point-to-point interfaces
|
||||
}
|
||||
if iface.Flags&net.FlagMulticast == 0 {
|
||||
// Ignore non-multicast interfaces
|
||||
continue
|
||||
}
|
||||
if iface.Flags&net.FlagPointToPoint != 0 {
|
||||
// Ignore point-to-point interfaces
|
||||
continue
|
||||
}
|
||||
for _, ifcfg := range ifcfgs {
|
||||
for ifcfg := range m.config._interfaces {
|
||||
// Compile each regular expression
|
||||
e, err := regexp.Compile(ifcfg.Regex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Does the interface match the regular expression? Store it if so
|
||||
if e.MatchString(iface.Name) {
|
||||
if ifcfg.Beacon || ifcfg.Listen {
|
||||
info := interfaceInfo{
|
||||
iface: iface,
|
||||
beacon: ifcfg.Beacon,
|
||||
listen: ifcfg.Listen,
|
||||
port: ifcfg.Port,
|
||||
}
|
||||
interfaces[iface.Name] = info
|
||||
}
|
||||
break
|
||||
if !ifcfg.Beacon && !ifcfg.Listen {
|
||||
continue
|
||||
}
|
||||
if !ifcfg.Regex.MatchString(iface.Name) {
|
||||
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{
|
||||
iface: iface,
|
||||
beacon: ifcfg.Beacon,
|
||||
listen: ifcfg.Listen,
|
||||
port: ifcfg.Port,
|
||||
priority: ifcfg.Priority,
|
||||
password: []byte(ifcfg.Password),
|
||||
hash: hasher.Sum(nil),
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return interfaces
|
||||
}
|
||||
|
||||
func (m *Multicast) AnnounceNow() {
|
||||
phony.Block(m, func() {
|
||||
if m._timer != nil && !m._timer.Stop() {
|
||||
<-m._timer.C
|
||||
}
|
||||
m.Act(nil, m._announce)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Multicast) _announce() {
|
||||
if !m.isOpen {
|
||||
if !m.running.Load() {
|
||||
return
|
||||
}
|
||||
m._updateInterfaces()
|
||||
groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
|
||||
groupAddr, err := net.ResolveUDPAddr("udp6", string(m.config._groupAddr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
destAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
|
||||
destAddr, err := net.ResolveUDPAddr("udp6", string(m.config._groupAddr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// There might be interfaces that we configured listeners for but are no
|
||||
// longer up - if that's the case then we should stop the listeners
|
||||
for name, info := range m.listeners {
|
||||
for name, info := range m._listeners {
|
||||
// Prepare our stop function!
|
||||
stop := func() {
|
||||
info.listener.Stop()
|
||||
delete(m.listeners, name)
|
||||
info.listener.Cancel()
|
||||
delete(m._listeners, name)
|
||||
m.log.Debugln("No longer multicasting on", name)
|
||||
}
|
||||
// If the interface is no longer visible on the system then stop the
|
||||
|
@ -243,7 +276,7 @@ func (m *Multicast) _announce() {
|
|||
// 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
|
||||
found := false
|
||||
listenaddr, err := net.ResolveTCPAddr("tcp6", info.listener.Listener.Addr().String())
|
||||
listenaddr, err := net.ResolveTCPAddr("tcp6", info.listener.Addr().String())
|
||||
if err != nil {
|
||||
stop()
|
||||
continue
|
||||
|
@ -272,13 +305,9 @@ func (m *Multicast) _announce() {
|
|||
for _, info := range m._interfaces {
|
||||
iface := info.iface
|
||||
for _, addr := range info.addrs {
|
||||
addrIP, _, _ := net.ParseCIDR(addr.String())
|
||||
// Ignore IPv4 addresses
|
||||
if addrIP.To4() != nil {
|
||||
continue
|
||||
}
|
||||
// Ignore non-link-local addresses
|
||||
if !addrIP.IsLinkLocalUnicast() {
|
||||
addrIP, _, err := net.ParseCIDR(addr.String())
|
||||
// Ignore IPv4 addresses or non-link-local addresses
|
||||
if err != nil || addrIP.To4() != nil || !addrIP.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
if info.listen {
|
||||
|
@ -290,24 +319,27 @@ func (m *Multicast) _announce() {
|
|||
}
|
||||
// Try and see if we already have a TCP listener for this interface
|
||||
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
|
||||
urlString := fmt.Sprintf("tls://[%s]:%d", addrIP, info.port)
|
||||
u, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
v := &url.Values{}
|
||||
v.Add("priority", fmt.Sprintf("%d", info.priority))
|
||||
v.Add("password", string(info.password))
|
||||
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)
|
||||
// Store the listener so that we can stop it later if needed
|
||||
linfo = &listenerInfo{listener: li, time: time.Now()}
|
||||
m.listeners[iface.Name] = linfo
|
||||
linfo = &listenerInfo{listener: li, time: time.Now(), port: info.port}
|
||||
m._listeners[iface.Name] = linfo
|
||||
} else {
|
||||
m.log.Warnln("Not multicasting on", iface.Name, "due to error:", err)
|
||||
}
|
||||
} else {
|
||||
// An existing listener was found
|
||||
linfo = m.listeners[iface.Name]
|
||||
linfo = m._listeners[iface.Name]
|
||||
}
|
||||
// Make sure nothing above failed for some reason
|
||||
if linfo == nil {
|
||||
|
@ -316,37 +348,47 @@ func (m *Multicast) _announce() {
|
|||
if time.Since(linfo.time) < linfo.interval {
|
||||
continue
|
||||
}
|
||||
// Get the listener details and construct the multicast beacon
|
||||
lladdr := linfo.listener.Listener.Addr().String()
|
||||
if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil {
|
||||
a.Zone = ""
|
||||
destAddr.Zone = iface.Name
|
||||
msg := append([]byte(nil), m.core.GetSelf().Key...)
|
||||
msg = append(msg, a.IP...)
|
||||
pbs := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(pbs, uint16(a.Port))
|
||||
msg = append(msg, pbs...)
|
||||
_, _ = m.sock.WriteTo(msg, nil, destAddr)
|
||||
addr := linfo.listener.Addr().(*net.TCPAddr)
|
||||
adv := multicastAdvertisement{
|
||||
MajorVersion: core.ProtocolVersionMajor,
|
||||
MinorVersion: core.ProtocolVersionMinor,
|
||||
PublicKey: m.core.PublicKey(),
|
||||
Port: uint16(addr.Port),
|
||||
Hash: info.hash,
|
||||
}
|
||||
msg, err := adv.MarshalBinary()
|
||||
if err != nil {
|
||||
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 {
|
||||
linfo.interval += time.Second
|
||||
}
|
||||
linfo.time = time.Now()
|
||||
break
|
||||
}
|
||||
}
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Multicast) listen() {
|
||||
groupAddr, err := net.ResolveUDPAddr("udp6", m.groupAddr)
|
||||
groupAddr, err := net.ResolveUDPAddr("udp6", string(m.config._groupAddr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
bs := make([]byte, 2048)
|
||||
hb := make([]byte, 0, blake2b.Size) // Reused to reduce hash allocations
|
||||
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 !m.IsStarted() {
|
||||
return
|
||||
|
@ -364,40 +406,45 @@ func (m *Multicast) listen() {
|
|||
continue
|
||||
}
|
||||
}
|
||||
if nBytes < ed25519.PublicKeySize {
|
||||
var adv multicastAdvertisement
|
||||
if err := adv.UnmarshalBinary(bs[:n]); err != nil {
|
||||
continue
|
||||
}
|
||||
var key ed25519.PublicKey
|
||||
key = append(key, bs[:ed25519.PublicKeySize]...)
|
||||
if bytes.Equal(key, m.core.GetSelf().Key) {
|
||||
continue // don't bother trying to peer with self
|
||||
}
|
||||
begin := ed25519.PublicKeySize
|
||||
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 {
|
||||
switch {
|
||||
case adv.MajorVersion != core.ProtocolVersionMajor:
|
||||
continue
|
||||
case adv.MinorVersion != core.ProtocolVersionMinor:
|
||||
continue
|
||||
case adv.PublicKey.Equal(m.core.PublicKey()):
|
||||
continue
|
||||
}
|
||||
from := fromAddr.(*net.UDPAddr)
|
||||
if !from.IP.Equal(addr.IP) {
|
||||
continue
|
||||
}
|
||||
var interfaces map[string]interfaceInfo
|
||||
from.Port = int(adv.Port)
|
||||
var interfaces map[string]*interfaceInfo
|
||||
phony.Block(m, func() {
|
||||
interfaces = m._interfaces
|
||||
})
|
||||
if info, ok := interfaces[from.Zone]; ok && info.listen {
|
||||
addr.Zone = ""
|
||||
pin := fmt.Sprintf("/?key=%s", hex.EncodeToString(key))
|
||||
u, err := url.Parse("tls://" + addr.String() + pin)
|
||||
hasher, err := blake2b.New512(info.password)
|
||||
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 {
|
||||
m.log.Debugln("Call from multicast failed:", err)
|
||||
|
|
|
@ -1,48 +1,17 @@
|
|||
// +build darwin
|
||||
//go:build !cgo && (darwin || ios)
|
||||
// +build !cgo
|
||||
// +build darwin ios
|
||||
|
||||
package multicast
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation
|
||||
#import <Foundation/Foundation.h>
|
||||
NSNetServiceBrowser *serviceBrowser;
|
||||
void StartAWDLBrowsing() {
|
||||
if (serviceBrowser == nil) {
|
||||
serviceBrowser = [[NSNetServiceBrowser alloc] init];
|
||||
serviceBrowser.includesPeerToPeer = YES;
|
||||
}
|
||||
[serviceBrowser searchForServicesOfType:@"_yggdrasil._tcp" inDomain:@""];
|
||||
}
|
||||
void StopAWDLBrowsing() {
|
||||
if (serviceBrowser == nil) {
|
||||
return;
|
||||
}
|
||||
[serviceBrowser stop];
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func (m *Multicast) _multicastStarted() {
|
||||
if !m.isOpen {
|
||||
return
|
||||
}
|
||||
C.StopAWDLBrowsing()
|
||||
for intf := range m._interfaces {
|
||||
if intf == "awdl0" {
|
||||
C.StartAWDLBrowsing()
|
||||
break
|
||||
}
|
||||
}
|
||||
time.AfterFunc(time.Minute, func() {
|
||||
m.Act(nil, m._multicastStarted)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
|
||||
|
|
69
src/multicast/multicast_darwin_cgo.go
Normal file
69
src/multicast/multicast_darwin_cgo.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
//go:build (darwin && cgo) || (ios && cgo)
|
||||
// +build darwin,cgo ios,cgo
|
||||
|
||||
package multicast
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation
|
||||
#import <Foundation/Foundation.h>
|
||||
NSNetServiceBrowser *serviceBrowser;
|
||||
void StartAWDLBrowsing() {
|
||||
if (serviceBrowser == nil) {
|
||||
serviceBrowser = [[NSNetServiceBrowser alloc] init];
|
||||
serviceBrowser.includesPeerToPeer = YES;
|
||||
}
|
||||
[serviceBrowser searchForServicesOfType:@"_yggdrasil._tcp" inDomain:@""];
|
||||
}
|
||||
void StopAWDLBrowsing() {
|
||||
if (serviceBrowser == nil) {
|
||||
return;
|
||||
}
|
||||
[serviceBrowser stop];
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func (m *Multicast) _multicastStarted() {
|
||||
if !m.running.Load() {
|
||||
return
|
||||
}
|
||||
C.StopAWDLBrowsing()
|
||||
for intf := range m._interfaces {
|
||||
if intf == "awdl0" {
|
||||
C.StartAWDLBrowsing()
|
||||
break
|
||||
}
|
||||
}
|
||||
time.AfterFunc(time.Minute, func() {
|
||||
m.Act(nil, m._multicastStarted)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
|
||||
var control error
|
||||
var reuseport error
|
||||
var recvanyif error
|
||||
|
||||
control = c.Control(func(fd uintptr) {
|
||||
reuseport = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||
|
||||
// sys/socket.h: #define SO_RECV_ANYIF 0x1104
|
||||
recvanyif = unix.SetsockoptInt(int(fd), syscall.SOL_SOCKET, 0x1104, 1)
|
||||
})
|
||||
|
||||
switch {
|
||||
case reuseport != nil:
|
||||
return reuseport
|
||||
case recvanyif != nil:
|
||||
return recvanyif
|
||||
default:
|
||||
return control
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
// +build !linux,!darwin,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows
|
||||
//go:build !linux && !darwin && !ios && !netbsd && !freebsd && !openbsd && !dragonflybsd && !windows
|
||||
// +build !linux,!darwin,!ios,!netbsd,!freebsd,!openbsd,!dragonflybsd,!windows
|
||||
|
||||
package multicast
|
||||
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
//go:build linux || netbsd || freebsd || openbsd || dragonflybsd
|
||||
// +build linux netbsd freebsd openbsd dragonflybsd
|
||||
|
||||
package multicast
|
||||
|
||||
import "syscall"
|
||||
import "golang.org/x/sys/unix"
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func (m *Multicast) _multicastStarted() {
|
||||
|
||||
|
@ -11,15 +15,19 @@ func (m *Multicast) _multicastStarted() {
|
|||
|
||||
func (m *Multicast) multicastReuse(network string, address string, c syscall.RawConn) error {
|
||||
var control error
|
||||
var reuseport error
|
||||
var reuseaddr error
|
||||
|
||||
control = c.Control(func(fd uintptr) {
|
||||
reuseport = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||
// Previously we used SO_REUSEPORT here, but that meant that machines running
|
||||
// Yggdrasil nodes as different users would inevitably fail with EADDRINUSE.
|
||||
// The behaviour for multicast is similar with both, so we'll use SO_REUSEADDR
|
||||
// instead.
|
||||
reuseaddr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
||||
})
|
||||
|
||||
switch {
|
||||
case reuseport != nil:
|
||||
return reuseport
|
||||
case reuseaddr != nil:
|
||||
return reuseaddr
|
||||
default:
|
||||
return control
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package multicast
|
||||
|
||||
import "syscall"
|
||||
import "golang.org/x/sys/windows"
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func (m *Multicast) _multicastStarted() {
|
||||
|
||||
|
|
30
src/multicast/options.go
Normal file
30
src/multicast/options.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package multicast
|
||||
|
||||
import "regexp"
|
||||
|
||||
func (m *Multicast) _applyOption(opt SetupOption) {
|
||||
switch v := opt.(type) {
|
||||
case MulticastInterface:
|
||||
m.config._interfaces[v] = struct{}{}
|
||||
case GroupAddress:
|
||||
m.config._groupAddr = v
|
||||
}
|
||||
}
|
||||
|
||||
type SetupOption interface {
|
||||
isSetupOption()
|
||||
}
|
||||
|
||||
type MulticastInterface struct {
|
||||
Regex *regexp.Regexp
|
||||
Beacon bool
|
||||
Listen bool
|
||||
Port uint16
|
||||
Priority uint8
|
||||
Password string
|
||||
}
|
||||
|
||||
type GroupAddress string
|
||||
|
||||
func (a MulticastInterface) isSetupOption() {}
|
||||
func (a GroupAddress) isSetupOption() {}
|
45
src/tun/admin.go
Normal file
45
src/tun/admin.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package tun
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
|
||||
)
|
||||
|
||||
type GetTUNRequest struct{}
|
||||
type GetTUNResponse struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Name string `json:"name,omitempty"`
|
||||
MTU uint64 `json:"mtu,omitempty"`
|
||||
}
|
||||
|
||||
type TUNEntry struct {
|
||||
MTU uint64 `json:"mtu"`
|
||||
}
|
||||
|
||||
func (t *TunAdapter) getTUNHandler(req *GetTUNRequest, res *GetTUNResponse) error {
|
||||
res.Enabled = t.isEnabled
|
||||
if !t.isEnabled {
|
||||
return nil
|
||||
}
|
||||
res.Name = t.Name()
|
||||
res.MTU = t.MTU()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) {
|
||||
_ = a.AddHandler(
|
||||
"getTun", "Show information about the node's TUN interface", []string{},
|
||||
func(in json.RawMessage) (interface{}, error) {
|
||||
req := &GetTUNRequest{}
|
||||
res := &GetTUNResponse{}
|
||||
if err := json.Unmarshal(in, &req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.getTUNHandler(req, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
},
|
||||
)
|
||||
}
|
75
src/tun/iface.go
Normal file
75
src/tun/iface.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
wgtun "golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
const TUN_OFFSET_BYTES = 80 // sizeof(virtio_net_hdr)
|
||||
|
||||
func (tun *TunAdapter) read() {
|
||||
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 {
|
||||
n, err := tun.iface.Read(bufs, sizes, TUN_OFFSET_BYTES)
|
||||
if err != nil {
|
||||
if errors.Is(err, wgtun.ErrTooManySegments) {
|
||||
tun.log.Debugln("TUN segments dropped: %v", err)
|
||||
continue
|
||||
}
|
||||
tun.log.Errorln("Error reading TUN:", err)
|
||||
return
|
||||
}
|
||||
for i, b := range bufs[:n] {
|
||||
if _, err := tun.rwc.Write(b[TUN_OFFSET_BYTES : TUN_OFFSET_BYTES+sizes[i]]); err != nil {
|
||||
tun.log.Debugln("Unable to send packet:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tun *TunAdapter) queue() {
|
||||
for {
|
||||
p := bufPool.Get().([]byte)[:bufPoolSize]
|
||||
n, err := tun.rwc.Read(p)
|
||||
if err != nil {
|
||||
tun.log.Errorln("Exiting TUN writer due to core read error:", err)
|
||||
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 {
|
||||
continue // Nothing to do, the tun isn't enabled
|
||||
}
|
||||
if _, err := tun.iface.Write(bufs[:n], TUN_OFFSET_BYTES); err != nil {
|
||||
tun.Act(nil, func() {
|
||||
if !tun.isOpen {
|
||||
tun.log.Errorln("TUN iface write error:", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
24
src/tun/options.go
Normal file
24
src/tun/options.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package tun
|
||||
|
||||
func (m *TunAdapter) _applyOption(opt SetupOption) {
|
||||
switch v := opt.(type) {
|
||||
case InterfaceName:
|
||||
m.config.name = v
|
||||
case InterfaceMTU:
|
||||
m.config.mtu = v
|
||||
case FileDescriptor:
|
||||
m.config.fd = int32(v)
|
||||
}
|
||||
}
|
||||
|
||||
type SetupOption interface {
|
||||
isSetupOption()
|
||||
}
|
||||
|
||||
type InterfaceName string
|
||||
type InterfaceMTU uint64
|
||||
type FileDescriptor int32
|
||||
|
||||
func (a InterfaceName) isSetupOption() {}
|
||||
func (a InterfaceMTU) isSetupOption() {}
|
||||
func (a FileDescriptor) isSetupOption() {}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue