Compare commits

...

78 Commits

Author SHA1 Message Date
Neil Alexander
7ac38e3e58 Release: Yggdrasil 0.5.2 2023-11-06 09:25:15 +00:00
Neil
49c424ef21 Add -publickey command line switch (#1096)
Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2023-11-04 18:42:51 +00:00
Neil
0346af46da Don't panic when connect returns nil (fixes #1086) (#1089)
* Don't panic when connect returns `nil` (fixes #1086)

It isn't clear to me why this would happen but let's guard the condition anyway.

* Log inconsistent error state

---------

Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2023-11-04 18:42:42 +00:00
Neil
93a5adfd18 Add sockstls:// (#1090)
Closes #1087.

Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2023-11-04 17:57:15 +00:00
Neil
ddb75700a0 Report errors during handshake stage (#1091)
Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2023-11-04 17:57:04 +00:00
Neil
ae997a5acb Improve TUN setup logging (#1093) (#1095)
Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2023-11-04 17:56:52 +00:00
Arceliar
6a9c90d3eb Merge branch 'develop' of https://github.com/yggdrasil-network/yggdrasil-go into develop 2023-11-03 21:56:26 -05:00
Arceliar
41e045fe5b update ironwood dependency 2023-11-03 21:55:42 -05:00
Neil
e5e8c84d7c Merge pull request #1078 from yggdrasil-network/duplicate-peers
Don't panic at startup when duplicate peers are configured
2023-10-28 22:21:04 +01:00
Neil Alexander
e41b838d8f Don't panic at startup when duplicate peers are configured
Fixes #1077
2023-10-28 21:34:15 +01:00
Neil Alexander
7f9d4f3f6d Don't import LDFLAGS from the environment 2023-10-28 18:21:26 +01:00
Neil Alexander
a6b316ef08 Release: Yggdrasil 0.5.1 2023-10-28 16:21:50 +01:00
Neil Alexander
d781fef760 Release: Yggdrasil 0.5.0 2023-10-28 15:23:01 +01:00
Neil Alexander
b332664acb Release: Yggdrasil 0.5.0 2023-10-28 15:11:34 +01:00
Neil Alexander
01c1498bd5 Yggdrasil 0.5 release notes 2023-10-28 15:07:45 +01:00
Neil
0b578a637a Debian package updates (#1073)
* Update Debian package

* Don't put `AdminListen` in config by default, fix path in Debian package

* Fix path in unit file

* Preserve original service files for other packages

---------

Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2023-10-28 14:58:52 +01:00
Arceliar
82c54f87ea clean up some debug API output 2023-10-28 06:36:01 -05:00
Arceliar
d17ac39789 update ironwood dependency, add a debug API call for lookups 2023-10-28 05:26:43 -05:00
Neil Alexander
ea6ccf552f Update dependencies, test cross-builds for FreeBSD and OpenBSD in CI 2023-10-27 23:16:13 +01:00
Neil
1ac3d540e7 Merge pull request #1070 from Revertron/fix_mobile 2023-10-25 20:31:15 +01:00
Revertron
6873fd44ff Fixes logger, adds some log messages. 2023-10-25 20:59:19 +02:00
Neil Alexander
8afa737a8d Use ubuntu-20.04 image for router packages in CI 2023-10-24 22:44:33 +01:00
Neil Alexander
7934158f5f Use ubuntu-20.04 image for Debian packages in CI 2023-10-24 12:10:48 +01:00
Neil Alexander
a60771344a Remove DHT from yggdrasilctl help text (fixes #1069) 2023-10-23 23:42:31 +01:00
Neil Alexander
90c6288f7c Yggdrasil 0.5 RC3 2023-10-23 22:26:53 +01:00
Neil Alexander
094f80f39c Fix RetryPeersNow, move startup logging, don't set TUN address if not available 2023-10-22 15:51:30 +01:00
Neil Alexander
955aa4af79 Remove unnecessary pprof log line 2023-10-22 10:29:19 +01:00
Neil Alexander
73c6c25bd9 Restore removePeer method 2023-10-22 10:27:41 +01:00
Neil Alexander
80e56eafcd Allow PPROFLISTEN on all builds 2023-10-21 21:36:28 +01:00
Alex Akselrod
6a9493757d mobile: add support for Listen in config (#1063)
Co-authored-by: Neil <git@neilalexander.dev>
2023-10-21 17:33:17 +00:00
John Jolly
8ea20cd205 Add output for threadcount and key generation time to cmd/genkey
This change is to display information about the key generation process.

Specifically, two bits of information are now displayed
 * The number of threads created to search for keys, and
 * The time taken to generate a successful "next best" key
2023-10-21 18:21:47 +01:00
Neil Alexander
a2dffeff33 Version 0.5 RC2 release notes 2023-10-18 22:52:37 +01:00
Neil Alexander
a2053b51fe Yggdrasil 0.5 RC2 2023-10-18 22:44:14 +01:00
Neil Alexander
aceb037c57 Fix panic in mobile GetPeersJSON 2023-10-18 22:38:10 +01:00
Neil Alexander
bcd80b043f Don't tightloop when a listener can no longer accept connections 2023-10-17 21:41:21 +01:00
Neil Alexander
74ca02edfd Don't require TLS client certificate 2023-10-15 23:06:10 +01:00
Neil
e110dd46fd Yggdrasil 0.5 RC1 (merge future into develop)
Merge `future` into `develop`
2023-10-15 17:29:59 +01:00
Neil Alexander
88b773cd0a Version 0.5 RC1 release notes 2023-10-15 17:09:12 +01:00
Neil Alexander
efb4b4635d Don't send a TLS ALPN name 2023-10-14 20:26:30 +01:00
Neil Alexander
117e4b88f8 Fix panic on invalid handshake length 2023-10-12 19:12:17 +01:00
Neil Alexander
4b48fd0b5f Fix Windows TUN build 2023-10-12 00:08:16 +01:00
Neil
854cd75f04 Merge pull request #1042 from pfactum/syslog-no-timestamp
cmd/yggdrasil: do not log timestamps to syslog
2023-10-11 23:58:12 +01:00
Neil Alexander
4f656685ef Revert Wireguard TUN upgrade (needs work for vectorised reads) 2023-10-11 23:52:39 +01:00
Neil Alexander
ed8ba584e2 Update dependencies 2023-10-11 23:42:37 +01:00
Neil Alexander
2a21241738 Multicast passwords 2023-10-11 19:28:28 +01:00
Neil Alexander
45b773eade Remove TLS root validation
This is just too complicated compared to the per-peer/per-listener/per-interface password
approach.
2023-10-11 18:25:35 +01:00
Neil Alexander
6dc847de31 Merge branch 'neil/password' into future 2023-10-11 17:06:58 +01:00
Neil Alexander
bd7e699130 Add unit test for password auth 2023-10-09 22:28:20 +01:00
Neil Alexander
268ffbfd14 Add authenticated handshake, support for passwords 2023-10-09 17:17:12 +01:00
Neil Alexander
490c11c29e Fix more codefactor suggestions 2023-09-03 13:49:21 +01:00
Neil Alexander
991ea8b876 Fix codefactor suggestion 2023-09-03 13:32:15 +01:00
Neil Alexander
68d1036de8 Fix mobile unit test 2023-09-03 13:30:48 +01:00
Neil Alexander
fa3d943ba9 Don't set BBR for TCP peerings 2023-09-03 13:30:41 +01:00
Neil
9defa35c66 Merge branch 'develop' into future 2023-09-03 13:18:47 +01:00
Neil Alexander
c8b9aaeb67 Only set mobile memory limit on supported Go versions 2023-09-03 13:13:53 +01:00
Neil Alexander
8f3ab1d83c Merge branch 'develop' into future 2023-09-03 13:08:40 +01:00
Neil Alexander
12a3a8c73b Fix build tags for setupFD 2023-09-03 13:08:13 +01:00
Neil
6ab0639b82 Merge branch 'develop' into future 2023-09-03 12:58:55 +01:00
Neil Alexander
fbc5f62add Fix missing setupFD stubs 2023-08-17 14:08:03 +01:00
Neil Alexander
5b203ad8c5 Use Go 1.21 in CI, update minimum version to Go 1.20, lint fixes, update quic-go 2023-08-12 18:12:58 +01:00
Arceliar
fe14981dda update ironwood 2023-08-05 04:01:15 -05:00
Neil Alexander
63b214f6b7 Fix negotiating priority on connection 2023-07-15 22:34:29 +01:00
Neil Alexander
ff96740ac7 Fail to start if no configuration provided 2023-07-15 20:12:14 +01:00
Arceliar
7f94463332 Merge pull request #1037 from yggdrasil-network/neil/quic
QUIC interface support
2023-06-19 06:27:09 -05:00
Arceliar
bcbabff80f Merge pull request #1038 from yggdrasil-network/neil/multicast
Revise multicast format to include protocol version, discriminator for TLS roots
2023-06-19 06:26:58 -05:00
Arceliar
99dd8f85d3 Merge pull request #1046 from yggdrasil-network/neil/handshake
Tweak link handshake
2023-06-19 06:23:47 -05:00
Neil Alexander
57d9a2399f Revise multicast format to include protocol version, discriminator for TLS roots 2023-06-18 20:54:49 +01:00
Neil Alexander
423fc248d2 Remove debug lines 2023-06-18 20:54:16 +01:00
Neil Alexander
516fcce6b3 Keepalives are needed to stop the connection inactivity timeout 2023-06-18 20:54:16 +01:00
Neil Alexander
d8dc6b2670 QUIC interface support 2023-06-18 20:54:14 +01:00
Neil Alexander
109f59c7dc Tweak link handshake 2023-06-18 20:28:14 +01:00
Neil Alexander
002b984c04 Fix private key setup when certificate not specified 2023-06-18 18:10:27 +01:00
Neil Alexander
5e684550a8 Take interface in tun.New 2023-06-18 15:45:04 +01:00
Neil
80724438c9 Merge pull request #1045 from yggdrasil-network/neil/tunintf
Define interface for RWCs
2023-06-18 15:43:16 +01:00
Neil Alexander
b0f8d8af13 Define interface for RWCs 2023-06-18 15:36:14 +01:00
Arceliar
31177f5a73 Merge pull request #1044 from yggdrasil-network/arc/linkfix
Fix duplicate connections
2023-06-18 08:49:20 -05:00
Oleksandr Natalenko
f6c0d8406d cmd/yggdrasil: do not log timestamps to syslog
It is expected a syslog implementation be it rsyslog or journald to
have their own timestamping, so there's no point in duplicating that
info.

Signed-off-by: Oleksandr Natalenko <oleksandr@natalenko.name>
2023-06-08 21:44:46 +02:00
Neil Alexander
db9b57c052 Update contrib/mobile for the latest iOS build 2023-06-06 22:11:49 +01:00
49 changed files with 1512 additions and 885 deletions

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: 1.21
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
@@ -51,7 +51,7 @@ jobs:
strategy:
fail-fast: false
matrix:
goversion: ["1.19", "1.20"]
goversion: ["1.20", "1.21"]
name: Build & Test (Linux, Go ${{ matrix.goversion }})
needs: [lint]
@@ -75,7 +75,7 @@ jobs:
strategy:
fail-fast: false
matrix:
goversion: ["1.19", "1.20"]
goversion: ["1.20", "1.21"]
name: Build & Test (Windows, Go ${{ matrix.goversion }})
needs: [lint]
@@ -99,7 +99,7 @@ jobs:
strategy:
fail-fast: false
matrix:
goversion: ["1.19", "1.20"]
goversion: ["1.20", "1.21"]
name: Build & Test (macOS, Go ${{ matrix.goversion }})
needs: [lint]
@@ -119,6 +119,32 @@ jobs:
- name: Unit tests
run: go test -v ./...
build-freebsd:
strategy:
fail-fast: false
matrix:
goversion: ["1.20", "1.21"]
goos:
- freebsd
- openbsd
name: Build (Cross ${{ matrix.goos }}, Go ${{ matrix.goversion }})
needs: [lint]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
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]

View File

@@ -16,7 +16,7 @@ jobs:
name: Package (Debian, ${{ matrix.pkgarch }})
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
with:
@@ -107,7 +107,7 @@ jobs:
name: Package (Router, ${{ matrix.pkgarch }})
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
with:

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,7 @@ import (
"fmt"
"net"
"runtime"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
@@ -27,6 +28,8 @@ type keySet struct {
func main() {
threads := runtime.GOMAXPROCS(0)
fmt.Println("Threads:", threads)
start := time.Now()
var currentBest ed25519.PublicKey
newKeys := make(chan keySet, threads)
for i := 0; i < threads; i++ {
@@ -36,7 +39,7 @@ func main() {
newKey := <-newKeys
if isBetter(currentBest, newKey.pub) || len(currentBest) == 0 {
currentBest = newKey.pub
fmt.Println("-----")
fmt.Println("-----", time.Since(start))
fmt.Println("Priv:", hex.EncodeToString(newKey.priv))
fmt.Println("Pub:", hex.EncodeToString(newKey.pub))
addr := address.AddrForKey(newKey.pub)

View File

@@ -44,14 +44,13 @@ func main() {
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")
exportcsr := flag.Bool("exportcsr", false, "use in combination with either -useconf or -useconffile, outputs your self-signed certificate request in PEM format")
exportcert := flag.Bool("exportcert", false, "use in combination with either -useconf or -useconffile, outputs your self-signed certificate 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, "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")
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")
flag.Parse()
@@ -69,7 +68,7 @@ func main() {
case "syslog":
if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", version.BuildName()); err == nil {
logger = log.New(syslogger, "", log.Flags())
logger = log.New(syslogger, "", log.Flags()&^(log.Ldate|log.Ltime))
}
default:
@@ -115,6 +114,7 @@ func main() {
_ = f.Close()
case *genconf:
cfg.AdminListen = ""
var bs []byte
if *confjson {
bs, err = json.MarshalIndent(cfg, "", " ")
@@ -134,6 +134,7 @@ func main() {
if *getaddr || *getsnet {
fmt.Println("\nError: You need to specify some config data using -useconf or -useconffile.")
}
return
}
privateKey := ed25519.PrivateKey(cfg.PrivateKey)
@@ -155,7 +156,12 @@ func main() {
fmt.Println(ipnet.String())
return
case *getpkey:
fmt.Println(hex.EncodeToString(publicKey))
return
case *normaliseconf:
cfg.AdminListen = ""
var bs []byte
if *confjson {
bs, err = json.MarshalIndent(cfg, "", " ")
@@ -175,22 +181,6 @@ func main() {
}
fmt.Println(string(pem))
return
case *exportcsr:
pem, err := cfg.GenerateCertificateSigningRequest()
if err != nil {
panic(err)
}
fmt.Println(string(pem))
return
case *exportcert:
pem, err := cfg.MarshalPEMCertificate()
if err != nil {
panic(err)
}
fmt.Println(string(pem))
return
}
n := &node{}
@@ -212,9 +202,6 @@ func main() {
options = append(options, core.Peer{URI: peer, SourceInterface: intf})
}
}
for _, root := range cfg.RootCertificates {
options = append(options, core.RootCertificate(*root))
}
for _, allowed := range cfg.AllowedPublicKeys {
k, err := hex.DecodeString(allowed)
if err != nil {
@@ -225,6 +212,10 @@ func main() {
if n.core, err = core.New(cfg.Certificate, logger, options...); err != nil {
panic(err)
}
address, subnet := n.core.Address(), n.core.Subnet()
logger.Infof("Your public key is %s", hex.EncodeToString(n.core.PublicKey()))
logger.Infof("Your IPv6 address is %s", address.String())
logger.Infof("Your IPv6 subnet is %s", subnet.String())
}
// Setup the admin socket.
@@ -232,6 +223,9 @@ func main() {
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)
}
@@ -250,6 +244,7 @@ func main() {
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 {

View File

@@ -39,8 +39,8 @@ func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() {
fmt.Println(" - ", os.Args[0], "list")
fmt.Println(" - ", os.Args[0], "getPeers")
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")
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")

BIN
contrib/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -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/
@@ -68,35 +71,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 +130,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 \

View File

@@ -37,7 +37,7 @@ if [ $IOS ]; then
echo "Building framework for iOS"
go get golang.org/x/mobile/bind
gomobile bind \
-target ios -tags mobile -o Yggdrasil.xcframework \
-target ios,macos -tags mobile -o Yggdrasil.xcframework \
-ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \
./contrib/mobile ./src/config;
fi

View File

@@ -13,6 +13,7 @@ import (
"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"
_ "golang.org/x/mobile/bind"
@@ -28,7 +29,9 @@ type Yggdrasil struct {
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
@@ -39,10 +42,13 @@ func (m *Yggdrasil) StartAutoconfigure() error {
// 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
@@ -65,19 +71,24 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
}
options = append(options, core.AllowedPublicKey(k[:]))
}
for _, root := range m.config.RootCertificates {
options = append(options, core.RootCertificate(*root))
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())
}
// Setup 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{
@@ -86,9 +97,11 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
Listen: intf.Listen,
Port: intf.Port,
Priority: uint8(intf.Priority),
Password: intf.Password,
})
}
m.multicast, err = multicast.New(m.core, logger, options...)
logger.Infof("Starting multicast %s", "")
m.multicast, err = multicast.New(m.core, m.logger, options...)
if err != nil {
logger.Errorln("An error occurred starting multicast:", err)
}
@@ -149,10 +162,20 @@ func (m *Yggdrasil) RecvBuffer(buf []byte) (int, error) {
func (m *Yggdrasil) Stop() error {
logger := log.New(m.log, "", 0)
logger.EnableLevel("info")
logger.Infof("Stop the mobile Yggdrasil instance %s", "")
if err := m.multicast.Stop(); err != nil {
return err
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
}
@@ -200,8 +223,11 @@ func (m *Yggdrasil) GetPeersJSON() (result string) {
IP string
}{}
for _, v := range m.core.GetPeers() {
a := address.AddrForKey(v.Key)
ip := net.IP(a[:]).String()
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

View File

@@ -15,6 +15,8 @@ void Log(const char *text) {
import "C"
import (
"unsafe"
"github.com/yggdrasil-network/yggdrasil-go/src/tun"
)
type MobileLogger struct {
@@ -26,3 +28,13 @@ func (nsl MobileLogger) Write(p []byte) (n int, err error) {
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
}

View File

@@ -0,0 +1,10 @@
//go:build go1.20
// +build go1.20
package mobile
import "runtime/debug"
func setMemLimitIfPossible() {
debug.SetMemoryLimit(1024 * 1024 * 40)
}

View File

@@ -0,0 +1,8 @@
//go:build !go1.20
// +build !go1.20
package mobile
func setMemLimitIfPossible() {
// not supported by this Go version
}

View File

@@ -1,9 +1,21 @@
package mobile
import "testing"
import (
"os"
"testing"
"github.com/gologme/log"
)
func TestStartYggdrasil(t *testing.T) {
ygg := &Yggdrasil{}
logger := log.New(os.Stdout, "", 0)
logger.EnableLevel("error")
logger.EnableLevel("warn")
logger.EnableLevel("info")
ygg := &Yggdrasil{
logger: logger,
}
if err := ygg.StartAutoconfigure(); err != nil {
t.Fatalf("Failed to start Yggdrasil: %s", err)
}

View 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

View 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

42
go.mod
View File

@@ -1,39 +1,47 @@
module github.com/yggdrasil-network/yggdrasil-go
go 1.19
go 1.20
require (
github.com/Arceliar/ironwood v0.0.0-20230521174855-fdfa6326d125
github.com/Arceliar/ironwood v0.0.0-20231104025256-ec84c695fc44
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d
github.com/cheggaaa/pb/v3 v3.0.8
github.com/gologme/log v1.2.0
github.com/cheggaaa/pb/v3 v3.1.4
github.com/gologme/log v1.3.0
github.com/hashicorp/go-syslog v1.0.0
github.com/hjson/hjson-go/v4 v4.3.0
github.com/kardianos/minwinsvc v1.0.2
github.com/quic-go/quic-go v0.39.3
github.com/vishvananda/netlink v1.1.0
golang.org/x/mobile v0.0.0-20221110043201-43a038452099
golang.org/x/net v0.9.0
golang.org/x/sys v0.7.0
golang.org/x/text v0.9.0
golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a
golang.zx2c4.com/wireguard/windows v0.4.12
golang.org/x/crypto v0.14.0
golang.org/x/mobile v0.0.0-20231006135142-2b44d11868fe
golang.org/x/net v0.17.0
golang.org/x/sys v0.13.0
golang.org/x/text v0.13.0
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675
golang.zx2c4.com/wireguard/windows v0.5.3
)
require (
github.com/bits-and-blooms/bitset v1.5.0 // indirect
github.com/bits-and-blooms/bloom/v3 v3.3.1 // indirect
github.com/mattn/go-colorable v0.1.8 // 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/quic-go/qtls-go1-20 v0.3.4 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/tools v0.6.0 // indirect
go.uber.org/mock v0.3.0 // indirect
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/tools v0.14.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
)
require (
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/fatih/color v1.12.0 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/olekukonko/tablewriter v0.0.5
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
)

108
go.sum
View File

@@ -1,8 +1,7 @@
github.com/Arceliar/ironwood v0.0.0-20230521174855-fdfa6326d125 h1:l2elyrosw63mTqZzwR0Nv8vPZWZC/0Hvwl8Iuva5htM=
github.com/Arceliar/ironwood v0.0.0-20230521174855-fdfa6326d125/go.mod h1:5x7fWW0mshe9WQ1lvSMmmHBYC3BeHH9gpwW5tz7cbfw=
github.com/Arceliar/ironwood v0.0.0-20231104025256-ec84c695fc44 h1:u328GAZGtL0W4oFWQs4YWHZT215LL6Lw9aYJxx76UVs=
github.com/Arceliar/ironwood v0.0.0-20231104025256-ec84c695fc44/go.mod h1:5x7fWW0mshe9WQ1lvSMmmHBYC3BeHH9gpwW5tz7cbfw=
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.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/bits-and-blooms/bitset v1.3.1/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
@@ -10,33 +9,56 @@ github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD
github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bits-and-blooms/bloom/v3 v3.3.1 h1:K2+A19bXT8gJR5mU7y+1yW6hsKfNCjcP2uNfLFKncjQ=
github.com/bits-and-blooms/bloom/v3 v3.3.1/go.mod h1:bhUUknWd5khVbTe4UgMCSiOOVJzr3tMoijSK3WwvW90=
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/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo=
github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA=
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/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.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
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/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/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
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/v4 v4.3.0 h1:dyrzJdqqFGhHt+FSrs5n9s6b0fPM8oSJdWo+oS3YnJw=
github.com/hjson/hjson-go/v4 v4.3.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.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-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.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/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.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/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/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/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/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/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.39.3 h1:o3YB6t2SR+HU/pgwF29kJ6g4jJIJEwEZ8CKia1h1TKg=
github.com/quic-go/quic-go v0.39.3/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
@@ -45,38 +67,46 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17
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=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/mobile v0.0.0-20221110043201-43a038452099 h1:aIu0lKmfdgtn2uTj7JI2oN4TUrQvgB+wzTPO23bCKt8=
golang.org/x/mobile v0.0.0-20221110043201-43a038452099/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mobile v0.0.0-20231006135142-2b44d11868fe h1:lrXv4yHeD9FA8PSJATWowP1QvexpyAPWmPia+Kbzql8=
golang.org/x/mobile v0.0.0-20231006135142-2b44d11868fe/go.mod h1:BrnXpEObnFxpaT75Jo9hsCazwOWcp7nVIa8NNuH5cuA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -85,15 +115,23 @@ 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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a h1:tTbyylK9/D3u/wEP26Vx7L700UpY48nhioJWZM1vhZw=
golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU=
golang.zx2c4.com/wireguard/windows v0.4.12 h1:CUmbdWKVNzTSsVb4yUAiEwL3KsabdJkEPdDjCHxBlhA=
golang.zx2c4.com/wireguard/windows v0.4.12/go.mod h1:PW4y+d9oY83XU9rRwRwrJDwEMuhVjMxu2gfD1cfzS7w=
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-20230223181233-21636207a675 h1:/J/RVnr7ng4fWPRH3xa4WtBJ1Jp+Auu4YNLmGiPv5QU=
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675/go.mod h1:whfbyDBt09xhCYQWtO2+3UVjlaq6/9hDZrjg2ZE6SyA=
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.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
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=

View File

@@ -3,13 +3,13 @@ package address
import (
"bytes"
"crypto/ed25519"
"math/rand"
"crypto/rand"
"testing"
)
func TestAddress_Address_IsValid(t *testing.T) {
var address Address
rand.Read(address[:])
_, _ = rand.Read(address[:])
address[0] = 0
@@ -32,7 +32,7 @@ func TestAddress_Address_IsValid(t *testing.T) {
func TestAddress_Subnet_IsValid(t *testing.T) {
var subnet Subnet
rand.Read(subnet[:])
_, _ = rand.Read(subnet[:])
subnet[0] = 0

View File

@@ -1,9 +1,24 @@
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()
}
}
@@ -14,3 +29,51 @@ type SetupOption interface {
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
},
)
}

View File

@@ -1,5 +1,10 @@
package admin
import (
"fmt"
"net/url"
)
type RemovePeerRequest struct {
Uri string `json:"uri"`
Sintf string `json:"interface,omitempty"`
@@ -8,5 +13,9 @@ type RemovePeerRequest struct {
type RemovePeerResponse struct{}
func (a *AdminSocket) removePeerHandler(req *RemovePeerRequest, res *RemovePeerResponse) error {
return a.core.RemovePeer(req.Uri, req.Sintf)
u, err := url.Parse(req.Uri)
if err != nil {
return fmt.Errorf("unable to parse peering URI: %w", err)
}
return a.core.RemovePeer(u, req.Sintf)
}

View File

@@ -40,22 +40,20 @@ import (
// 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 {
PrivateKey KeyBytes `comment:"Your private key. DO NOT share this with anyone!"`
PrivateKeyPath string `json:",omitempty"`
Certificate *tls.Certificate `json:"-"`
CertificatePath string `json:",omitempty"`
RootCertificates []*x509.Certificate `json:"-"`
RootCertificatePaths []string `json:",omitempty"`
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."`
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."`
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."`
PrivateKey KeyBytes `comment:"Your private key. DO NOT share this with anyone!"`
PrivateKeyPath string `json:",omitempty"`
Certificate *tls.Certificate `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 `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. 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."`
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."`
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."`
LogLookups bool `json:",omitempty"`
}
type MulticastInterfaceConfig struct {
@@ -64,6 +62,7 @@ type MulticastInterfaceConfig struct {
Listen bool
Port uint16
Priority uint64 // really uint8, but gobind won't export it
Password string
}
// Generates default configuration and returns a pointer to the resulting
@@ -138,53 +137,18 @@ func (cfg *NodeConfig) postprocessConfig() error {
return err
}
}
if cfg.CertificatePath != "" {
if cfg.PrivateKeyPath == "" {
return fmt.Errorf("CertificatePath requires PrivateKeyPath")
}
cfg.Certificate = nil
f, err := os.ReadFile(cfg.CertificatePath)
if err != nil {
return err
}
if err := cfg.UnmarshalPEMCertificate(f); err != nil {
return err
}
}
if cfg.Certificate == nil {
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
}
}
cfg.RootCertificates = cfg.RootCertificates[:0]
for _, path := range cfg.RootCertificatePaths {
f, err := os.ReadFile(path)
if err != nil {
return err
}
if err := cfg.UnmarshalRootCertificate(f); err != nil {
return err
}
}
return nil
}
func (cfg *NodeConfig) UnmarshalRootCertificate(b []byte) error {
p, _ := pem.Decode(b)
if p == nil {
return fmt.Errorf("failed to parse PEM file")
}
if p.Type != "CERTIFICATE" {
return fmt.Errorf("unexpected PEM type %q", p.Type)
}
cert, err := x509.ParseCertificate(p.Bytes)
if err != nil {
return fmt.Errorf("failed to load X.509 keypair: %w", err)
}
if !cert.IsCA {
return fmt.Errorf("supplied root certificate is not a certificate authority")
}
cfg.RootCertificates = append(cfg.RootCertificates, cert)
return nil
}
@@ -208,26 +172,6 @@ func (cfg *NodeConfig) GenerateSelfSignedCertificate() error {
return nil
}
func (cfg *NodeConfig) GenerateCertificateSigningRequest() ([]byte, error) {
template := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: hex.EncodeToString(cfg.PrivateKey),
},
SignatureAlgorithm: x509.PureEd25519,
}
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, template, ed25519.PrivateKey(cfg.PrivateKey))
if err != nil {
return nil, err
}
pemBytes := bytes.NewBuffer(nil)
if err := pem.Encode(pemBytes, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}); err != nil {
return nil, err
}
return pemBytes.Bytes(), nil
}
func (cfg *NodeConfig) MarshalPEMCertificate() ([]byte, error) {
privateKey := ed25519.PrivateKey(cfg.PrivateKey)
publicKey := privateKey.Public().(ed25519.PublicKey)
@@ -256,15 +200,6 @@ func (cfg *NodeConfig) MarshalPEMCertificate() ([]byte, error) {
return pem.EncodeToMemory(block), nil
}
func (cfg *NodeConfig) UnmarshalPEMCertificate(b []byte) error {
tlsCert, err := tls.LoadX509KeyPair(cfg.CertificatePath, cfg.PrivateKeyPath)
if err != nil {
return fmt.Errorf("failed to load X.509 keypair: %w", err)
}
cfg.Certificate = &tlsCert
return nil
}
func (cfg *NodeConfig) NewPrivateKey() {
_, spriv, err := ed25519.GenerateKey(nil)
if err != nil {

View File

@@ -3,7 +3,6 @@ package core
import (
"crypto/ed25519"
"encoding/json"
"fmt"
"net"
"net/url"
"sync/atomic"
@@ -192,28 +191,8 @@ func (c *Core) AddPeer(u *url.URL, sintf string) error {
// RemovePeer removes a peer. The peer should be specified in URI format, see AddPeer.
// The peer is not disconnected immediately.
func (c *Core) RemovePeer(uri string, sourceInterface string) error {
return fmt.Errorf("not implemented yet")
/*
var err error
phony.Block(c, func() {
peer := Peer{uri, sourceInterface}
linkInfo, ok := c.config._peers[peer]
if !ok {
err = fmt.Errorf("peer not configured")
return
}
if ok && linkInfo != nil {
c.links.Act(nil, func() {
if link := c.links._links[*linkInfo]; link != nil {
_ = link.conn.Close()
}
})
}
delete(c.config._peers, peer)
})
return err
*/
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,

View File

@@ -4,8 +4,6 @@ import (
"context"
"crypto/ed25519"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"fmt"
"io"
"net"
@@ -39,8 +37,7 @@ type Core struct {
log Logger
addPeerTimer *time.Timer
config struct {
tls *tls.Config // immutable after startup
roots *x509.CertPool // immutable after startup
tls *tls.Config // immutable after startup
//_peers map[Peer]*linkInfo // configurable after startup
_listeners map[ListenAddress]struct{} // configurable after startup
nodeinfo NodeInfo // immutable after startup
@@ -106,13 +103,6 @@ func New(cert *tls.Certificate, logger Logger, opts ...SetupOption) (*Core, erro
); err != nil {
return nil, fmt.Errorf("error creating encryption: %w", err)
}
address, subnet := c.Address(), c.Subnet()
c.log.Infof("Your public key is %s", hex.EncodeToString(c.public))
c.log.Infof("Your IPv6 address is %s", address.String())
c.log.Infof("Your IPv6 subnet is %s", subnet.String())
if c.config.roots != nil {
c.log.Println("Yggdrasil is running in TLS-only mode")
}
c.proto.init(c)
if err := c.links.init(c); err != nil {
return nil, fmt.Errorf("error initialising links: %w", err)
@@ -145,7 +135,14 @@ func New(cert *tls.Certificate, logger Logger, opts ...SetupOption) (*Core, erro
}
func (c *Core) RetryPeersNow() {
// TODO: figure out a way to retrigger peer connections.
phony.Block(&c.links, func() {
for _, l := range c.links._links {
select {
case l.kick <- struct{}{}:
default:
}
}
})
}
// Stop shuts down the Yggdrasil node.
@@ -169,10 +166,6 @@ func (c *Core) _close() error {
return err
}
func (c *Core) isTLSOnly() bool {
return c.config.roots != nil
}
func (c *Core) MTU() uint64 {
const sessionTypeOverhead = 1
MTU := c.PacketConn.MTU() - sessionTypeOverhead

View File

@@ -2,7 +2,7 @@ package core
import (
"bytes"
"math/rand"
"crypto/rand"
"net/url"
"os"
"testing"
@@ -146,7 +146,7 @@ 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())
@@ -178,7 +178,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())

View File

@@ -1,6 +1,3 @@
//go:build debug
// +build debug
package core
import (
@@ -8,28 +5,13 @@ import (
"net/http"
_ "net/http/pprof"
"os"
"runtime"
"github.com/gologme/log"
)
// Start the profiler in debug builds, if the required environment variable is set.
// Start the profiler if the required environment variable is set.
func init() {
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 fmt.Println(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
}

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"context"
"encoding/hex"
"errors"
"fmt"
"io"
"math"
@@ -18,6 +17,7 @@ import (
"github.com/Arceliar/phony"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"golang.org/x/crypto/blake2b"
)
type linkType int
@@ -35,12 +35,13 @@ type links struct {
tls *linkTLS // TLS interface support
unix *linkUNIX // UNIX interface support
socks *linkSOCKS // SOCKS interface support
quic *linkQUIC // QUIC interface support
// _links can only be modified safely from within the links actor
_links map[linkInfo]*link // *link is nil if connection in progress
}
type linkProtocol interface {
dial(url *url.URL, info linkInfo, options linkOptions) (net.Conn, error)
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)
}
@@ -52,9 +53,11 @@ type linkInfo struct {
// link tracks the state of a connection, either persistent or non-persistent
type link struct {
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
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
@@ -65,6 +68,7 @@ type linkOptions struct {
pinnedEd25519Keys map[keyArray]struct{}
priority uint8
tlsSNI string
password []byte
}
type Listener struct {
@@ -90,6 +94,7 @@ func (l *links) init(c *Core) error {
l.tls = l.newLinkTLS(l.tcp)
l.unix = l.newLinkUNIX()
l.socks = l.newLinkSOCKS()
l.quic = l.newLinkQUIC()
l._links = make(map[linkInfo]*link)
var listeners []ListenAddress
@@ -126,8 +131,10 @@ 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("password is invalid")
const ErrLinkUnrecognisedSchema = linkError("link schema unknown")
func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
@@ -165,6 +172,29 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
}
options.priority = uint8(pi)
}
if p := u.Query().Get("password"); p != "" {
if len(p) > blake2b.Size {
retErr = ErrLinkPasswordInvalid
return
}
options.password = []byte(p)
}
// 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,
@@ -188,6 +218,7 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
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
@@ -207,12 +238,14 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
backoff++
duration := time.Second * time.Duration(math.Exp2(float64(backoff)))
select {
case <-time.After(duration):
return true
case <-state.kick:
return true
case <-state.ctx.Done():
return false
case <-l.core.ctx.Done():
return false
case <-time.After(duration):
return true
}
}
@@ -221,20 +254,29 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
// then the loop will run endlessly, using backoffs as needed.
// Otherwise the loop will end, cleaning up the link entry.
go func() {
defer func() {
phony.Block(l, func() {
if l._links[info] == state {
delete(l._links, info)
}
})
}()
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 {
conn, err := l.connect(u, info, options)
if err != nil {
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
@@ -250,15 +292,13 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
// the next connection.
if backoffNow() {
continue
} else {
return
}
} else {
// Ephemeral and incoming connections don't remain
// after a connection failure, so exit out of the
// loop and clean up the link entry.
break
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
@@ -307,11 +347,8 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
if linkType == linkTypePersistent {
if backoffNow() {
continue
} else {
return
}
} else {
break
return
}
}
}()
@@ -319,6 +356,33 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
return retErr
}
func (l *links) remove(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,
}
// 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) (*Listener, error) {
ctx, cancel := context.WithCancel(l.core.ctx)
var protocol linkProtocol
@@ -329,6 +393,8 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
protocol = l.tls
case "unix":
protocol = l.unix
case "quic":
protocol = l.quic
default:
cancel()
return nil, ErrLinkUnrecognisedSchema
@@ -352,6 +418,12 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
}
options.priority = uint8(pi)
}
if p := u.Query().Get("password"); p != "" {
if len(p) > blake2b.Size {
return nil, ErrLinkPasswordInvalid
}
options.password = []byte(p)
}
go func() {
l.core.log.Printf("%s listener started on %s", strings.ToUpper(u.Scheme), listener.Addr())
@@ -359,7 +431,7 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
for {
conn, err := listener.Accept()
if err != nil {
continue
return
}
go func(conn net.Conn) {
defer conn.Close()
@@ -438,43 +510,33 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
return li, nil
}
func (l *links) connect(u *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
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":
// 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
}
}
dialer = l.tls
case "socks":
case "socks", "sockstls":
dialer = l.socks
case "unix":
dialer = l.unix
case "quic":
dialer = l.quic
default:
return nil, ErrLinkUnrecognisedSchema
}
return dialer.dial(u, info, options)
return dialer.dial(ctx, u, info, options)
}
func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn) error {
meta := version_getBaseMetadata()
meta.publicKey = l.core.public
metaBytes := meta.encode()
meta.priority = options.priority
metaBytes, err := meta.encode(l.core.secret, options.password)
if err != nil {
return fmt.Errorf("failed to generate handshake: %w", err)
}
if err := conn.SetDeadline(time.Now().Add(time.Second * 6)); err != nil {
return fmt.Errorf("failed to set handshake deadline: %w", err)
}
@@ -485,16 +547,11 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn) e
case err == nil && n != len(metaBytes):
return fmt.Errorf("incomplete handshake send")
}
if _, err = io.ReadFull(conn, metaBytes); err != nil {
return fmt.Errorf("read handshake: %w", err)
}
if err = conn.SetDeadline(time.Time{}); err != nil {
return fmt.Errorf("failed to clear handshake deadline: %w", err)
}
meta = version_metadata{}
base := version_getBaseMetadata()
if !meta.decode(metaBytes) {
return errors.New("failed to decode metadata")
if err := meta.decode(conn, options.password); err != nil {
_ = conn.Close()
return err
}
if !meta.check() {
return fmt.Errorf("remote node incompatible version (local %s, remote %s)",
@@ -502,6 +559,9 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn) e
fmt.Sprintf("%d.%d", meta.majorVer, meta.minorVer),
)
}
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 := options.pinnedEd25519Keys; len(pinned) > 0 {
@@ -534,10 +594,14 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn) e
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
}
l.core.log.Infof("Connected %s: %s, source %s",
dir, remoteStr, localStr)
err = l.core.HandleConn(meta.publicKey, conn, options.priority)
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",

96
src/core/link_quic.go Normal file
View File

@@ -0,0 +1,96 @@
package core
import (
"context"
"crypto/tls"
"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.EarlyListener
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) {
qc, err := quic.DialAddrEarly(ctx, url.Host, l.tlsconfig, l.quicconfig)
if err != nil {
return nil, err
}
qs, err := qc.OpenStream()
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.ListenAddrEarly(url.Host, l.tlsconfig, l.quicconfig)
if err != nil {
return nil, err
}
ch := make(chan *linkQUICStream)
lql := &linkQUICListener{
EarlyListener: ql,
ch: ch,
}
go func() {
for {
qc, err := ql.Accept(ctx)
if err != nil {
ql.Close()
return
}
qs, err := qc.AcceptStream(ctx)
if err != nil {
ql.Close()
return
}
ch <- &linkQUICStream{
Connection: qc,
Stream: qs,
}
}
}()
return lql, nil
}

View File

@@ -2,6 +2,7 @@ package core
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/url"
@@ -21,7 +22,7 @@ func (l *links) newLinkSOCKS() *linkSOCKS {
return lt
}
func (l *linkSOCKS) dial(url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
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{
@@ -34,7 +35,16 @@ func (l *linkSOCKS) dial(url *url.URL, info linkInfo, options linkOptions) (net.
return nil, fmt.Errorf("failed to configure proxy")
}
pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/")
return dialer.Dial("tcp", pathtokens[0])
conn, err := dialer.Dial("tcp", pathtokens[0])
if err != nil {
return nil, fmt.Errorf("failed to dial: %w", err)
}
if url.Scheme == "sockstls" {
tlsconfig := l.tls.config.Clone()
tlsconfig.ServerName = options.tlsSNI
conn = tls.Client(conn, tlsconfig)
}
return conn, nil
}
func (l *linkSOCKS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {

View File

@@ -68,10 +68,7 @@ func (l *linkTCP) dialersFor(url *url.URL, info linkInfo) ([]*tcpDialer, error)
return dialers, nil
}
func (l *linkTCP) dial(url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
if l.core.isTLSOnly() {
return nil, fmt.Errorf("TCP peer prohibited in TLS-only mode")
}
func (l *linkTCP) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
dialers, err := l.dialersFor(url, info)
if err != nil {
return nil, err
@@ -81,7 +78,7 @@ func (l *linkTCP) dial(url *url.URL, info linkInfo, options linkOptions) (net.Co
}
for _, d := range dialers {
var conn net.Conn
conn, err = d.dialer.DialContext(l.core.ctx, "tcp", d.addr.String())
conn, err = d.dialer.DialContext(ctx, "tcp", d.addr.String())
if err != nil {
l.core.log.Warnf("Failed to connect to %s: %s", d.addr, err)
continue
@@ -92,9 +89,6 @@ func (l *linkTCP) dial(url *url.URL, info linkInfo, options linkOptions) (net.Co
}
func (l *linkTCP) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
if l.core.isTLSOnly() {
return nil, fmt.Errorf("TCP listener prohibited in TLS-only mode")
}
hostport := url.Host
if sintf != "" {
if host, port, err := net.SplitHostPort(hostport); err == nil {

View File

@@ -12,22 +12,6 @@ import (
// 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 {
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
}

View File

@@ -33,7 +33,7 @@ func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS {
return lt
}
func (l *linkTLS) dial(url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
func (l *linkTLS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
dialers, err := l.tcp.dialersFor(url, info)
if err != nil {
return nil, err
@@ -49,7 +49,7 @@ func (l *linkTLS) dial(url *url.URL, info linkInfo, options linkOptions) (net.Co
Config: tlsconfig,
}
var conn net.Conn
conn, err = tlsdialer.DialContext(l.core.ctx, "tcp", d.addr.String())
conn, err = tlsdialer.DialContext(ctx, "tcp", d.addr.String())
if err != nil {
continue
}

View File

@@ -32,12 +32,12 @@ func (l *links) newLinkUNIX() *linkUNIX {
return lt
}
func (l *linkUNIX) dial(url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
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(l.core.ctx, "unix", addr.String())
return l.dialer.DialContext(ctx, "unix", addr.String())
}
func (l *linkUNIX) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {

View File

@@ -2,25 +2,26 @@ package core
import (
"crypto/ed25519"
"crypto/x509"
"fmt"
"net/url"
)
func (c *Core) _applyOption(opt SetupOption) (err error) {
switch v := opt.(type) {
case RootCertificate:
cert := x509.Certificate(v)
if c.config.roots == nil {
c.config.roots = x509.NewCertPool()
}
c.config.roots.AddCert(&cert)
case Peer:
u, err := url.Parse(v.URI)
if err != nil {
return fmt.Errorf("unable to parse peering URI: %w", err)
}
return c.links.add(u, v.SourceInterface, linkTypePersistent)
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 NodeInfo:
@@ -39,7 +40,6 @@ type SetupOption interface {
isSetupOption()
}
type RootCertificate x509.Certificate
type ListenAddress string
type Peer struct {
URI string
@@ -49,7 +49,6 @@ type NodeInfo map[string]interface{}
type NodeInfoPrivacy bool
type AllowedPublicKey ed25519.PublicKey
func (a RootCertificate) isSetupOption() {}
func (a ListenAddress) isSetupOption() {}
func (a Peer) isSetupOption() {}
func (a NodeInfo) isSetupOption() {}

41
src/core/options_test.go Normal file
View 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")
}
}

View File

@@ -3,13 +3,12 @@ package core
import (
"crypto/tls"
"crypto/x509"
"fmt"
)
func (c *Core) generateTLSConfig(cert *tls.Certificate) (*tls.Config, error) {
config := &tls.Config{
Certificates: []tls.Certificate{*cert},
ClientAuth: tls.RequireAnyClientCert,
ClientAuth: tls.NoClientCert,
GetClientCertificate: func(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) {
return cert, nil
},
@@ -21,43 +20,10 @@ func (c *Core) generateTLSConfig(cert *tls.Certificate) (*tls.Config, error) {
return config, nil
}
func (c *Core) verifyTLSCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
if c.config.roots == nil {
// If there's no certificate pool configured then we will
// accept all TLS certificates.
return nil
}
if len(rawCerts) == 0 {
return fmt.Errorf("expected at least one certificate")
}
opts := x509.VerifyOptions{
Roots: c.config.roots,
}
for i, rawCert := range rawCerts {
if i == 0 {
// The first certificate is the leaf certificate. All other
// certificates in the list are intermediates, so add them
// into the VerifyOptions.
continue
}
cert, err := x509.ParseCertificate(rawCert)
if err != nil {
return fmt.Errorf("failed to parse intermediate certificate: %w", err)
}
opts.Intermediates.AddCert(cert)
}
cert, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return fmt.Errorf("failed to parse leaf certificate: %w", err)
}
_, err = cert.Verify(opts)
return err
}
func (c *Core) verifyTLSConnection(cs tls.ConnectionState) error {
func (c *Core) verifyTLSCertificate(_ [][]byte, _ [][]*x509.Certificate) error {
return nil
}
func (c *Core) verifyTLSConnection(_ tls.ConnectionState) error {
return nil
}

View File

@@ -8,6 +8,10 @@ import (
"bytes"
"crypto/ed25519"
"encoding/binary"
"fmt"
"io"
"golang.org/x/crypto/blake2b"
)
// This is the version-specific metadata exchanged at the start of a connection.
@@ -25,6 +29,8 @@ const (
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
@@ -41,9 +47,10 @@ func version_getBaseMetadata() version_metadata {
}
// Encodes version metadata into its wire format.
func (m *version_metadata) encode() []byte {
func (m *version_metadata) encode(privateKey ed25519.PrivateKey, password []byte) ([]byte, error) {
bs := make([]byte, 0, 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)
@@ -61,16 +68,46 @@ func (m *version_metadata) encode() []byte {
bs = binary.BigEndian.AppendUint16(bs, 1)
bs = append(bs, m.priority)
return bs
hasher, err := blake2b.New512(password)
if err != nil {
return nil, err
}
n, err := hasher.Write(m.publicKey)
if err != nil {
return nil, err
}
if n != ed25519.PublicKeySize {
return nil, fmt.Errorf("hash writer only wrote %d bytes", n)
}
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 {
meta := [4]byte{'m', 'e', 't', 'a'}
if !bytes.Equal(bs[:4], meta[:]) {
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
}
for bs = bs[4:]; len(bs) >= 4; {
meta := [4]byte{'m', 'e', 't', 'a'}
if !bytes.Equal(bh[:4], meta[:]) {
return fmt.Errorf("invalid handshake preamble")
}
hl := binary.BigEndian.Uint16(bh[4:6])
if hl < ed25519.SignatureSize {
return fmt.Errorf("invalid handshake length")
}
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) {
@@ -92,7 +129,20 @@ func (m *version_metadata) decode(bs []byte) bool {
}
bs = bs[oplen:]
}
return true
hasher, err := blake2b.New512(password)
if err != nil {
return fmt.Errorf("invalid password supplied")
}
n, err := hasher.Write(m.publicKey)
if err != nil || n != ed25519.PublicKeySize {
return fmt.Errorf("failed to generate hash")
}
hash := hasher.Sum(nil)
if !ed25519.Verify(m.publicKey, hash, sig) {
return fmt.Errorf("password is incorrect")
}
return nil
}
// Checks that the "meta" bytes and the version numbers are the expected values.

View File

@@ -1,34 +1,78 @@
package core
import (
"bytes"
"crypto/ed25519"
"math/rand"
"reflect"
"testing"
)
func TestVersionRoundtrip(t *testing.T) {
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},
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
} {
// Generate a random public key for each time, since it is
// a required field.
test.publicKey = make(ed25519.PublicKey, ed25519.PublicKeySize)
rand.Read(test.publicKey)
encoded := test.encode()
decoded := &version_metadata{}
if !decoded.decode(encoded) {
t.Fatalf("failed to decode")
pk1, sk1, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatalf("Node 1 failed to generate key: %s", err)
}
if !reflect.DeepEqual(test, decoded) {
t.Fatalf("round-trip failed\nwant: %+v\n got: %+v", test, decoded)
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)
}
}
}
}

View File

@@ -7,22 +7,33 @@ import (
)
type multicastAdvertisement struct {
PublicKey ed25519.PublicKey
Port uint16
MajorVersion uint16
MinorVersion uint16
PublicKey ed25519.PublicKey
Port uint16
Hash []byte
}
func (m *multicastAdvertisement) MarshalBinary() ([]byte, error) {
b := make([]byte, 0, ed25519.PublicKeySize+2)
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+2 {
if len(b) < ed25519.PublicKeySize+8 {
return fmt.Errorf("invalid multicast beacon")
}
m.PublicKey = b[:ed25519.PublicKeySize]
m.Port = binary.BigEndian.Uint16(b[ed25519.PublicKeySize:])
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
}

View 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")
}
}

View File

@@ -1,7 +1,9 @@
package multicast
import (
"bytes"
"context"
"crypto/ed25519"
"encoding/hex"
"fmt"
"math/rand"
@@ -13,6 +15,7 @@ import (
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/core"
"golang.org/x/crypto/blake2b"
"golang.org/x/net/ipv6"
)
@@ -42,6 +45,8 @@ type interfaceInfo struct {
listen bool
port uint16
priority uint8
password []byte
hash []byte
}
type listenerInfo struct {
@@ -175,6 +180,7 @@ func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo {
return nil
}
// Work out which interfaces to announce on
pk := m.core.PublicKey()
for _, iface := range allifaces {
switch {
case iface.Flags&net.FlagUp == 0:
@@ -193,12 +199,23 @@ func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo {
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
}
@@ -295,10 +312,13 @@ func (m *Multicast) _announce() {
var linfo *listenerInfo
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 {
m.log.Debugln("Started multicasting on", iface.Name)
@@ -321,8 +341,11 @@ func (m *Multicast) _announce() {
}
addr := linfo.listener.Addr().(*net.TCPAddr)
adv := multicastAdvertisement{
PublicKey: m.core.PublicKey(),
Port: uint16(addr.Port),
MajorVersion: core.ProtocolVersionMajor,
MinorVersion: core.ProtocolVersionMinor,
PublicKey: m.core.PublicKey(),
Port: uint16(addr.Port),
Hash: info.hash,
}
msg, err := adv.MarshalBinary()
if err != nil {
@@ -350,6 +373,7 @@ func (m *Multicast) listen() {
panic(err)
}
bs := make([]byte, 2048)
hb := make([]byte, 0, blake2b.Size) // Reused to reduce hash allocations
for {
n, rcm, fromAddr, err := m.sock.ReadFrom(bs)
if err != nil {
@@ -373,7 +397,12 @@ func (m *Multicast) listen() {
if err := adv.UnmarshalBinary(bs[:n]); err != nil {
continue
}
if adv.PublicKey.Equal(m.core.PublicKey()) {
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)
@@ -383,9 +412,22 @@ func (m *Multicast) listen() {
interfaces = m._interfaces
})
if info, ok := interfaces[from.Zone]; ok && info.listen {
hasher, err := blake2b.New512(info.password)
if err != nil {
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(),

View File

@@ -21,6 +21,7 @@ type MulticastInterface struct {
Listen bool
Port uint16
Priority uint8
Password string
}
type GroupAddress string

View File

@@ -6,6 +6,8 @@ func (m *TunAdapter) _applyOption(opt SetupOption) {
m.config.name = v
case InterfaceMTU:
m.config.mtu = v
case FileDescriptor:
m.config.fd = int32(v)
}
}
@@ -15,6 +17,8 @@ type SetupOption interface {
type InterfaceName string
type InterfaceMTU uint64
type FileDescriptor int32
func (a InterfaceName) isSetupOption() {}
func (a InterfaceMTU) isSetupOption() {}
func (a InterfaceName) isSetupOption() {}
func (a InterfaceMTU) isSetupOption() {}
func (a FileDescriptor) isSetupOption() {}

View File

@@ -8,6 +8,7 @@ package tun
import (
"errors"
"fmt"
"io"
"net"
"github.com/Arceliar/phony"
@@ -16,27 +17,34 @@ import (
"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"
)
type MTU uint16
type ReadWriteCloser interface {
io.ReadWriteCloser
Address() address.Address
Subnet() address.Subnet
MaxMTU() uint64
SetMTU(uint64)
}
// TunAdapter represents a running TUN interface and extends the
// yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you
// should pass this object to the yggdrasil.SetRouterAdapter() function before
// calling yggdrasil.Start().
type TunAdapter struct {
rwc *ipv6rwc.ReadWriteCloser
rwc ReadWriteCloser
log core.Logger
addr address.Address
subnet address.Subnet
mtu uint64
iface tun.Device
phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below
//mutex sync.RWMutex // Protects the below
isOpen bool
isEnabled bool // Used by the writer to drop sessionTraffic if not enabled
config struct {
isOpen bool
isEnabled bool // Used by the writer to drop sessionTraffic if not enabled
config struct {
fd int32
name InterfaceName
mtu InterfaceMTU
}
@@ -90,7 +98,7 @@ func MaximumMTU() uint64 {
// Init initialises the TUN module. You must have acquired a Listener from
// the Yggdrasil core before this point and it must not be in use elsewhere.
func New(rwc *ipv6rwc.ReadWriteCloser, log core.Logger, opts ...SetupOption) (*TunAdapter, error) {
func New(rwc ReadWriteCloser, log core.Logger, opts ...SetupOption) (*TunAdapter, error) {
tun := &TunAdapter{
rwc: rwc,
log: log,
@@ -108,7 +116,10 @@ func (tun *TunAdapter) _start() error {
tun.addr = tun.rwc.Address()
tun.subnet = tun.rwc.Subnet()
prefix := address.GetPrefix()
addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(prefix[:])-1)
var addr string
if tun.addr.IsValid() {
addr = fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(prefix[:])-1)
}
if tun.config.name == "none" || tun.config.name == "dummy" {
tun.log.Debugln("Not starting TUN as ifname is none or dummy")
tun.isEnabled = false
@@ -119,7 +130,13 @@ func (tun *TunAdapter) _start() error {
if tun.rwc.MaxMTU() < mtu {
mtu = tun.rwc.MaxMTU()
}
if err := tun.setup(string(tun.config.name), addr, mtu); err != nil {
var err error
if tun.config.fd > 0 {
err = tun.setupFD(tun.config.fd, addr, mtu)
} else {
err = tun.setup(string(tun.config.name), addr, mtu)
}
if err != nil {
return err
}
if tun.MTU() != mtu {

View File

@@ -5,6 +5,7 @@ package tun
import (
"encoding/binary"
"fmt"
"os/exec"
"strconv"
"strings"
@@ -77,7 +78,7 @@ type in6_ifreq_lifetime struct {
func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
iface, err := wgtun.CreateTUN(ifname, int(mtu))
if err != nil {
panic(err)
return fmt.Errorf("failed to create TUN: %w", err)
}
tun.iface = iface
if mtu, err := iface.MTU(); err == nil {
@@ -85,7 +86,15 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
} else {
tun.mtu = 0
}
return tun.setupAddress(addr)
if addr != "" {
return tun.setupAddress(addr)
}
return nil
}
// Configures the "utun" adapter from an existing file descriptor.
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
return fmt.Errorf("setup via FD not supported on this platform")
}
func (tun *TunAdapter) setupAddress(addr string) error {

View File

@@ -1,5 +1,5 @@
//go:build !mobile
// +build !mobile
//go:build darwin || ios
// +build darwin ios
package tun
@@ -7,6 +7,8 @@ package tun
import (
"encoding/binary"
"fmt"
"os"
"strconv"
"strings"
"unsafe"
@@ -23,7 +25,7 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
}
iface, err := wgtun.CreateTUN(ifname, int(mtu))
if err != nil {
panic(err)
return fmt.Errorf("failed to create TUN: %w", err)
}
tun.iface = iface
if m, err := iface.MTU(); err == nil {
@@ -31,7 +33,35 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
} else {
tun.mtu = 0
}
return tun.setupAddress(addr)
if addr != "" {
return tun.setupAddress(addr)
}
return nil
}
// Configures the "utun" adapter from an existing file descriptor.
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
dfd, err := unix.Dup(int(fd))
if err != nil {
return fmt.Errorf("failed to duplicate FD: %w", err)
}
err = unix.SetNonblock(dfd, true)
if err != nil {
unix.Close(dfd)
return fmt.Errorf("failed to set FD as non-blocking: %w", err)
}
iface, err := wgtun.CreateTUNFromFile(os.NewFile(uintptr(dfd), "/dev/tun"), 0)
if err != nil {
unix.Close(dfd)
return fmt.Errorf("failed to create TUN from FD: %w", err)
}
tun.iface = iface
if m, err := iface.MTU(); err == nil {
tun.mtu = getSupportedMTU(uint64(m))
} else {
tun.mtu = 0
}
return nil // tun.setupAddress(addr)
}
const (
@@ -82,7 +112,7 @@ func (tun *TunAdapter) setupAddress(addr string) error {
if fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil {
tun.log.Printf("Create AF_SYSTEM socket failed: %v.", err)
return err
return fmt.Errorf("failed to open AF_SYSTEM: %w", err)
}
var ar in6_aliasreq
@@ -117,16 +147,16 @@ func (tun *TunAdapter) setupAddress(addr string) error {
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", ir.ifru_mtu)
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 {
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(darwin_SIOCAIFADDR_IN6), uintptr(unsafe.Pointer(&ar))); errno != 0 { // nolint:staticcheck
err = errno
tun.log.Errorf("Error in darwin_SIOCAIFADDR_IN6: %v", errno)
return err
return fmt.Errorf("failed to call SIOCAIFADDR_IN6: %w", err)
}
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 { // nolint:staticcheck
err = errno
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
return err
return fmt.Errorf("failed to call SIOCSIFMTU: %w", err)
}
return err

View File

@@ -1,11 +1,13 @@
//go:build !mobile
// +build !mobile
//go:build linux || android
// +build linux android
package tun
// The linux platform specific tun parts
import (
"fmt"
"github.com/vishvananda/netlink"
wgtun "golang.zx2c4.com/wireguard/tun"
)
@@ -17,7 +19,7 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
}
iface, err := wgtun.CreateTUN(ifname, int(mtu))
if err != nil {
panic(err)
return fmt.Errorf("failed to create TUN: %w", err)
}
tun.iface = iface
if mtu, err := iface.MTU(); err == nil {
@@ -25,7 +27,15 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
} else {
tun.mtu = 0
}
return tun.setupAddress(addr)
if addr != "" {
return tun.setupAddress(addr)
}
return nil
}
// Configures the "utun" adapter from an existing file descriptor.
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
return fmt.Errorf("setup via FD not supported on this platform")
}
// Configures the TUN adapter with the correct IPv6 address and MTU. Netlink
@@ -35,20 +45,20 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
func (tun *TunAdapter) setupAddress(addr string) error {
nladdr, err := netlink.ParseAddr(addr)
if err != nil {
return err
return fmt.Errorf("couldn't parse address %q: %w", addr, err)
}
nlintf, err := netlink.LinkByName(tun.Name())
if err != nil {
return err
return fmt.Errorf("failed to find link by name: %w", err)
}
if err := netlink.AddrAdd(nlintf, nladdr); err != nil {
return err
return fmt.Errorf("failed to add address to link: %w", err)
}
if err := netlink.LinkSetMTU(nlintf, int(tun.mtu)); err != nil {
return err
return fmt.Errorf("failed to set link MTU: %w", err)
}
if err := netlink.LinkSetUp(nlintf); err != nil {
return err
return fmt.Errorf("failed to bring link up: %w", err)
}
// Friendly output
tun.log.Infof("Interface name: %s", tun.Name())

View File

@@ -1,5 +1,5 @@
//go:build !linux && !darwin && !windows && !openbsd && !freebsd && !mobile
// +build !linux,!darwin,!windows,!openbsd,!freebsd,!mobile
//go:build !linux && !darwin && !ios && !android && !windows && !openbsd && !freebsd && !mobile
// +build !linux,!darwin,!ios,!android,!windows,!openbsd,!freebsd,!mobile
package tun
@@ -7,6 +7,8 @@ package tun
// If your platform supports tun devices, you could try configuring it manually
import (
"fmt"
wgtun "golang.zx2c4.com/wireguard/tun"
)
@@ -14,7 +16,7 @@ import (
func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
iface, err := wgtun.CreateTUN(ifname, mtu)
if err != nil {
panic(err)
return fmt.Errorf("failed to create TUN: %w", err)
}
tun.iface = iface
if mtu, err := iface.MTU(); err == nil {
@@ -22,7 +24,15 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
} else {
tun.mtu = 0
}
return tun.setupAddress(addr)
if addr != "" {
return tun.setupAddress(addr)
}
return nil
}
// Configures the "utun" adapter from an existing file descriptor.
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
return fmt.Errorf("setup via FD not supported on this platform")
}
// We don't know how to set the IPv6 address on an unknown platform, therefore

View File

@@ -4,10 +4,10 @@
package tun
import (
"bytes"
"errors"
"fmt"
"log"
"net"
"net/netip"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"golang.org/x/sys/windows"
@@ -35,9 +35,11 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
return err
}
tun.iface = iface
if err = tun.setupAddress(addr); err != nil {
tun.log.Errorln("Failed to set up TUN address:", err)
return err
if addr != "" {
if err = tun.setupAddress(addr); err != nil {
tun.log.Errorln("Failed to set up TUN address:", err)
return err
}
}
if err = tun.setupMTU(getSupportedMTU(mtu)); err != nil {
tun.log.Errorln("Failed to set up TUN MTU:", err)
@@ -50,6 +52,11 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
})
}
// Configures the "utun" adapter from an existing file descriptor.
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
return fmt.Errorf("setup via FD not supported on this platform")
}
// Sets the MTU of the TUN adapter.
func (tun *TunAdapter) setupMTU(mtu uint64) error {
if tun.iface == nil || tun.Name() == "" {
@@ -83,13 +90,9 @@ func (tun *TunAdapter) setupAddress(addr string) error {
return errors.New("Can't configure IPv6 address as TUN adapter is not present")
}
if intf, ok := tun.iface.(*wgtun.NativeTun); ok {
if ipaddr, ipnet, err := net.ParseCIDR(addr); err == nil {
if ipnet, err := netip.ParsePrefix(addr); err == nil {
luid := winipcfg.LUID(intf.LUID())
addresses := append([]net.IPNet{}, net.IPNet{
IP: ipaddr,
Mask: ipnet.Mask,
})
addresses := []netip.Prefix{ipnet}
err := luid.SetIPAddressesForFamily(windows.AF_INET6, addresses)
if err == windows.ERROR_OBJECT_ALREADY_EXISTS {
cleanupAddressesOnDisconnectedInterfaces(windows.AF_INET6, addresses)
@@ -112,24 +115,13 @@ func (tun *TunAdapter) setupAddress(addr string) error {
* SPDX-License-Identifier: MIT
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
*/
func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []net.IPNet) {
func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []netip.Prefix) {
if len(addresses) == 0 {
return
}
includedInAddresses := func(a net.IPNet) bool {
// TODO: this makes the whole algorithm O(n^2). But we can't stick net.IPNet in a Go hashmap. Bummer!
for _, addr := range addresses {
ip := addr.IP
if ip4 := ip.To4(); ip4 != nil {
ip = ip4
}
mA, _ := addr.Mask.Size()
mB, _ := a.Mask.Size()
if bytes.Equal(ip, a.IP) && mA == mB {
return true
}
}
return false
addrHash := make(map[netip.Addr]bool, len(addresses))
for i := range addresses {
addrHash[addresses[i].Addr()] = true
}
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault)
if err != nil {
@@ -140,11 +132,10 @@ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, add
continue
}
for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
ip := address.Address.IP()
ipnet := net.IPNet{IP: ip, Mask: net.CIDRMask(int(address.OnLinkPrefixLength), 8*len(ip))}
if includedInAddresses(ipnet) {
log.Printf("Cleaning up stale address %s from interface %s", ipnet.String(), iface.FriendlyName())
iface.LUID.DeleteIPAddress(ipnet)
if ip, _ := netip.AddrFromSlice(address.Address.IP()); addrHash[ip] {
prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength))
log.Printf("Cleaning up stale address %s from interface %s", prefix.String(), iface.FriendlyName())
iface.LUID.DeleteIPAddress(prefix)
}
}
}