Compare commits

..

131 commits

Author SHA1 Message Date
Sergey Alirzaev
47818a1a7c
apparmor: add yggdrasilctl policy (#1235)
Some checks failed
Yggdrasil / Lint (push) Has been cancelled
Yggdrasil / Analyse (push) Has been cancelled
Yggdrasil / Build & Test (Linux, Go 1.22) (push) Has been cancelled
Yggdrasil / Build & Test (Linux, Go 1.23) (push) Has been cancelled
Yggdrasil / Build & Test (Linux, Go 1.24) (push) Has been cancelled
Yggdrasil / Build & Test (Windows, Go 1.22) (push) Has been cancelled
Yggdrasil / Build & Test (Windows, Go 1.23) (push) Has been cancelled
Yggdrasil / Build & Test (Windows, Go 1.24) (push) Has been cancelled
Yggdrasil / Build & Test (macOS, Go 1.22) (push) Has been cancelled
Yggdrasil / Build & Test (macOS, Go 1.23) (push) Has been cancelled
Yggdrasil / Build & Test (macOS, Go 1.24) (push) Has been cancelled
Yggdrasil / Build (Cross freebsd, Go 1.22) (push) Has been cancelled
Yggdrasil / Build (Cross freebsd, Go 1.23) (push) Has been cancelled
Yggdrasil / Build (Cross freebsd, Go 1.24) (push) Has been cancelled
Yggdrasil / Build (Cross openbsd, Go 1.22) (push) Has been cancelled
Yggdrasil / Build (Cross openbsd, Go 1.23) (push) Has been cancelled
Yggdrasil / Build (Cross openbsd, Go 1.24) (push) Has been cancelled
Yggdrasil / All tests passed (push) Has been cancelled
2025-04-15 17:17:52 +01:00
Sergey Alirzaev
6377d7f071
contrib/openrc: remove SIGHUP logic (#1236)
as it is long gone from the daemon code
and unexpectedly kills the daemon
2025-04-15 17:15:09 +01:00
Neil Alexander
5b8dbc8b1e
Add summary helpers to mobile wrapper
Some checks failed
Yggdrasil / Lint (push) Has been cancelled
Yggdrasil / Analyse (push) Has been cancelled
Yggdrasil / Build & Test (Linux, Go 1.22) (push) Has been cancelled
Yggdrasil / Build & Test (Linux, Go 1.23) (push) Has been cancelled
Yggdrasil / Build & Test (Linux, Go 1.24) (push) Has been cancelled
Yggdrasil / Build & Test (Windows, Go 1.22) (push) Has been cancelled
Yggdrasil / Build & Test (Windows, Go 1.23) (push) Has been cancelled
Yggdrasil / Build & Test (Windows, Go 1.24) (push) Has been cancelled
Yggdrasil / Build & Test (macOS, Go 1.22) (push) Has been cancelled
Yggdrasil / Build & Test (macOS, Go 1.23) (push) Has been cancelled
Yggdrasil / Build & Test (macOS, Go 1.24) (push) Has been cancelled
Yggdrasil / Build (Cross freebsd, Go 1.22) (push) Has been cancelled
Yggdrasil / Build (Cross freebsd, Go 1.23) (push) Has been cancelled
Yggdrasil / Build (Cross freebsd, Go 1.24) (push) Has been cancelled
Yggdrasil / Build (Cross openbsd, Go 1.22) (push) Has been cancelled
Yggdrasil / Build (Cross openbsd, Go 1.23) (push) Has been cancelled
Yggdrasil / Build (Cross openbsd, Go 1.24) (push) Has been cancelled
Yggdrasil / All tests passed (push) Has been cancelled
2025-03-31 10:18:57 +01:00
patrini32
73705ff09d
Typo fix (#1232)
Some checks failed
Yggdrasil / Analyse (push) Has been cancelled
Yggdrasil / Lint (push) Has been cancelled
Yggdrasil / Build & Test (Linux, Go 1.22) (push) Has been cancelled
Yggdrasil / Build & Test (Linux, Go 1.23) (push) Has been cancelled
Yggdrasil / Build & Test (Linux, Go 1.24) (push) Has been cancelled
Yggdrasil / Build & Test (Windows, Go 1.22) (push) Has been cancelled
Yggdrasil / Build & Test (Windows, Go 1.23) (push) Has been cancelled
Yggdrasil / Build & Test (Windows, Go 1.24) (push) Has been cancelled
Yggdrasil / Build & Test (macOS, Go 1.22) (push) Has been cancelled
Yggdrasil / Build & Test (macOS, Go 1.23) (push) Has been cancelled
Yggdrasil / Build & Test (macOS, Go 1.24) (push) Has been cancelled
Yggdrasil / Build (Cross freebsd, Go 1.22) (push) Has been cancelled
Yggdrasil / Build (Cross freebsd, Go 1.23) (push) Has been cancelled
Yggdrasil / Build (Cross freebsd, Go 1.24) (push) Has been cancelled
Yggdrasil / Build (Cross openbsd, Go 1.22) (push) Has been cancelled
Yggdrasil / Build (Cross openbsd, Go 1.23) (push) Has been cancelled
Yggdrasil / Build (Cross openbsd, Go 1.24) (push) Has been cancelled
Yggdrasil / All tests passed (push) Has been cancelled
2025-02-20 09:45:49 +00:00
Neil Alexander
3b18909f70
Update dependencies
Some checks failed
Yggdrasil / Lint (push) Has been cancelled
Yggdrasil / Analyse (push) Has been cancelled
Yggdrasil / Build & Test (Linux, Go 1.22) (push) Has been cancelled
Yggdrasil / Build & Test (Linux, Go 1.23) (push) Has been cancelled
Yggdrasil / Build & Test (Linux, Go 1.24) (push) Has been cancelled
Yggdrasil / Build & Test (Windows, Go 1.22) (push) Has been cancelled
Yggdrasil / Build & Test (Windows, Go 1.23) (push) Has been cancelled
Yggdrasil / Build & Test (Windows, Go 1.24) (push) Has been cancelled
Yggdrasil / Build & Test (macOS, Go 1.22) (push) Has been cancelled
Yggdrasil / Build & Test (macOS, Go 1.23) (push) Has been cancelled
Yggdrasil / Build & Test (macOS, Go 1.24) (push) Has been cancelled
Yggdrasil / Build (Cross freebsd, Go 1.22) (push) Has been cancelled
Yggdrasil / Build (Cross freebsd, Go 1.23) (push) Has been cancelled
Yggdrasil / Build (Cross freebsd, Go 1.24) (push) Has been cancelled
Yggdrasil / Build (Cross openbsd, Go 1.22) (push) Has been cancelled
Yggdrasil / Build (Cross openbsd, Go 1.23) (push) Has been cancelled
Yggdrasil / Build (Cross openbsd, Go 1.24) (push) Has been cancelled
Yggdrasil / All tests passed (push) Has been cancelled
2025-02-18 12:57:58 +00:00
Neil Alexander
58b727d1f0
Add Go 1.24 to CI 2025-02-18 12:52:21 +00:00
Klemens Nanni
782c0250d7
Use pledge(2) on OpenBSD (#1215)
Straight forward thanks to all privileged operations being done early
enough during startup.
2024-12-22 11:04:26 +00:00
Neil Alexander
213f72b840
Yggdrasil 0.5.12 2024-12-18 22:34:30 +00:00
Neil Alexander
1fbcf3b3c2
Rename latency_ms to latency in getPeers response since it isn't even milliseconds anymore 2024-12-18 22:21:23 +00:00
Peter Gervai
22bc9c44e2
genkeys print the number of generated keys (#1217)
It is good to know how many resources have we carelessly wasted. :-)
2024-12-18 19:56:46 +00:00
Neil
9c73bacab9
Update to Go 1.22, quic-go/quic-go@v0.48.2 (#1218)
Our dependencies are now moving beyond Go 1.21 so need to update.

Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2024-12-13 23:33:26 +00:00
Neil Alexander
04be129878
Update to Arceliar/ironwood@743fe2f 2024-12-13 23:12:36 +00:00
Neil Alexander
657f7e0db3
Fix empty user/group detection on chuser
This should fix #1216.
2024-12-13 16:55:25 +00:00
Neil
7adf5f18b7
Yggdrasil 0.5.11 (#1214)
Changelog updates.

Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2024-12-12 19:26:54 +00:00
Neil Alexander
69451fe969
Specify TLS 1.2-TLS 1.3 supported range for client connections
Should fix #1208.
2024-12-12 19:07:55 +00:00
Klemens Nanni
2d587740c1
genkeys, yggdrasilctl: Use pledge(2) on OpenBSD (#1193)
Restrict system operations of CLI tools with
https://man.openbsd.org/pledge.2.

https://pkg.go.dev/suah.dev/protect abstracts the OS specific code, i.e.
is a NOOP on non-OpenBSD systems.

This PR is to gauge upstream interest in this direction; my OpenBSD port
of yggdrasil already pledges the daemon,
resulting in minimal runtime privileges, but there are still a few rough
edges:

https://github.com/jasperla/openbsd-wip/blob/master/net/yggdrasil/patches/patch-cmd_yggdrasil_main_go#L80

---------

Co-authored-by: Neil <git@neilalexander.dev>
2024-12-12 18:48:24 +00:00
Neil Alexander
b2b0396d48
Update dependencies 2024-12-12 18:42:53 +00:00
Klemens Nanni
83ec58afc7
Use unveil(2) on OpenBSD (#1194)
After #1175 removed ioctl(2) fallback code shelling out to ifconfig(8),
there is no code left (compiled on OpenBSD) that would fork(2) or
execve(2).

Drop the ability to run any executable file to double down on this, thus
reducing the attack surface of this this experimental, internet facing
daemon running as root.

pledge(2) is doable, but needs more polish.
unveil(2), however, is as simple as it gets.

On other systems, this code is a NOOP, but can still help to implement
similar safety belts.
2024-12-12 18:37:02 +00:00
Neil Alexander
b436052b2d
Update to Arceliar/ironwood@9deb08d 2024-12-10 19:02:13 +00:00
Neil
3ed4a92288
Yggdrasil 0.5.10 (#1207)
Changelog updates.

Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2024-11-24 12:56:24 +00:00
Neil Alexander
bdb2d399c5
Update dependencies 2024-11-23 14:55:14 +00:00
Neil Alexander
7790a19e4c
New detail in getMulticastInterfaces admin endpoint 2024-11-23 14:49:48 +00:00
Neil Alexander
d3b4de46ea
Improvements to how link shutdowns are handled 2024-11-23 13:43:34 +00:00
Neil Alexander
2454970e4d
Tweaks to configuration 2024-11-22 09:47:33 +00:00
Neil Alexander
b98f98318f
Tweaks to link handling 2024-11-22 09:44:30 +00:00
Neil Alexander
ff9e90c5aa
Update link cost calculation and next-hop selection (update to Arceliar/ironwood@75a6e82) 2024-11-22 09:31:38 +00:00
Neil
9398cae230
Expose download/upload rate per peer (#1206) 2024-11-19 08:42:27 +00:00
Klemens Nanni
c22a746a1d
Rewrite chuser() for simplicity and correctness (#1203)
- Use unambiguous variable names (w/o package name conflict).
- Fail on invalid input such as the empty string or `:`.
- Do not change group without user, i.e. fail on `:group`.
- Parse input using mnemonic APIs.
- Do not juggle between integer types.
- Unset supplementary groups.
- Use set[ug]id(2) to follow the idiom of OpenBSD base programs.
  (cannot use setres[ug]id(2) as macOS does not have them.)

Includes/Supersedes #1202.
Fixes #927.

I only tested on OpenBSD (so far), but other systems should just work.
2024-11-17 21:37:07 +00:00
Neil Alexander
67ec5a92b3
Fix some lint issues 2024-11-17 21:29:26 +00:00
Neil Alexander
42873be09b
Reusable peer lookup/dial logic 2024-11-17 21:14:54 +00:00
Klemens Nanni
75d2080e53
Set groups when dropping privileges to not leak supplementary group access (#1202)
Changing the real and effective user/group IDs and the saved
set-user/group-ID is not enough to get rid of intial access permissions.

The list of groups must be cleared also, otherwise a process changing
from, e.g. `root:root` to `nobody:nobody` retains rights to access
`:wheel` files (assuming `root` is a member of the `wheel` group).

For example:
```
# id
uid=0(root) gid=0(wheel) groups=0(wheel), 2(kmem), 3(sys), 4(tty), 5(operator), 20(staff), 31(guest)
# ./yggdrasil -autoconf -logto /dev/null -user nobody &
[1] 4337
# ps -o command,user,group,supgrp -U nobody
COMMAND          USER     GROUP    SUPGRP
./yggdrasil -aut nobody   nobody   wheel,kmem,sys,tty,operator,staff,guest
```

Fix that so the process runs as mere
```
COMMAND          USER     GROUP    SUPGRP
./yggdrasil -aut nobody   nobody   nobody
```

Fixes #927.
2024-11-11 19:28:28 +00:00
Klemens Nanni
834680045a
Create admin socket synchronously before privdrop (#1201)
Creating UNIX sockets the listen() goroutine that races against the main
one dropping to an unprivileged user may cause startup failure when
privdrop happens before privileged filesystem access.

Setup or fail in New() and only do listen(2) in listen() to avoid this.

```
# yggdrasil -autoconf -user nobody
2024/11/03 21:15:27 Build name: yggdrasil-go
2024/11/03 21:15:27 Build version: 0.5.9
...
2024/11/03 21:15:27 Admin socket failed to listen: listen unix /var/run/yggdrasil.sock: bind: permission denied
```

Rerun, now the order is flipped:
```
# yggdrasil -autoconf -user nobody
2024/11/03 21:15:34 Build name: yggdrasil-go
2024/11/03 21:15:34 Build version: 0.5.9
[...]
2024/11/03 21:15:34 UNIX admin socket listening on /var/run/yggdrasil.sock
[...]
```

Fixes #927.
2024-11-11 19:27:02 +00:00
Neil Alexander
eef613993f
Raise link error when SNI supplied on unsupported link type
Closes #1196
2024-10-27 21:06:56 +00:00
Neil Alexander
ff0ef7ff56
Update comments in default configuration file 2024-10-27 20:59:05 +00:00
Neil Alexander
ef110b0181
Update Debian package metadata 2024-10-27 20:38:15 +00:00
Neil Alexander
b20ad846a1
When IfName is none, start queue goroutine, otherwise iprwc blocks and some handlers don't run 2024-10-20 21:28:04 +01:00
Neil
0b9c8bd020
Yggdrasil 0.5.9 (#1191)
Changelog updates.

Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2024-10-19 17:09:46 +01:00
Neil Alexander
0b9469100c
Update dependencies 2024-10-17 13:23:11 +01:00
Klemens Nanni
a6429390da
Use UNIX socket patch from url struct (#1186)
No need to extract it again when the url package provides it for us:
```
$ jq -n '{"AdminListen":"unix:///tmp/ygg.sock"}' | ./yggdrasil -useconf | grep 'admin socket'
2024/10/08 22:41:11 UNIX admin socket listening on /tmp/ygg.sock
```

Follow-up on #1176
2024-10-17 13:22:46 +01:00
Klemens Nanni
1ee61dcefa
zap obsolete nonexistent command from usage (#1184) 2024-10-17 13:22:22 +01:00
Neil Alexander
81e345c1ae
Update to Arceliar/ironwood@f6fb9da97a 2024-10-16 09:46:36 +01:00
Neil Alexander
a038a6a8ef
Update to Arceliar/ironwood@4ea1ec6d68 2024-10-13 21:33:47 +01:00
Neil Alexander
01e73792fe
Update to Arceliar/ironwood@0ac2ff3eef 2024-10-13 20:06:07 +01:00
Neil Alexander
d22dc9ecc9
TUN: Skip ErrTooManySegments 2024-10-10 09:23:13 +01:00
Klemens Nanni
874083da79
Replace repeated subscripts with single TrimPrefix (#1176)
This stood out to me while reading the code: [7:] is skipping "unix://",
so why not do that?

Doing so reveals a bug in the last line changed, where chmod(2) failure
would print just the prefix, not everything but it... easy to miss, but
now this kind of bug can no longer happen.
2024-09-30 14:25:04 +01:00
Klemens Nanni
ccda1075c0
Fix ioctl(2) code for OpenBSD (#1175)
This cleans up the mess to configure an IP address on a tun(4) device.

Handrolling a hardcoded ioctl(2) request is far from perfect, but Go
(golang.org/sys/unix) is to blame here.

Tested on OpenBSD 7.6 -current where yggdrasil now drives the interface
would use of ifconfig or other helpers.
2024-09-30 14:24:20 +01:00
Neil Alexander
6d5243bd9a
Add unit test for AllowedPublicKeys 2024-09-29 22:04:41 +01:00
Neil Alexander
377bc664c9
The AllowedPublicKeys option should not apply to multicast listeners
Another fix for #1141.
2024-09-29 21:38:56 +01:00
Neil Alexander
d1b849588f
Fix bug where ephemeral links would try to reconnect in a fast loop
Helps #1141, although not a complete solution.
2024-09-29 21:24:39 +01:00
Sergey Bobrenok
d6fd305f12
Fix Android build with Go 1.23.0 or later (#1166)
The `github.com/wlynxg/anet` library depends on the `//go:linkname`
linker feature [1]. However, since Go 1.23.0, the usage of
`//go:linkname` has been restricted [2]. And now it's necessary to
explicitly specify `-checklinkname=0` linker flag to use it.

[1]
https://github.com/wlynxg/anet/blob/main/README.md#how-to-build-with-go-1230-or-later
[2] https://tip.golang.org/doc/go1.23#linker

Resolves: #1165
2024-09-29 21:06:36 +01:00
Klemens Nanni
98a6fdb4f2
tun: bsd: remove redundant ioctl to set MTU (#1172)
wireguard's CreateTUN() sets the MTU using the same ioctl(2), on both
FreeBSD and OpenBSD.

Tested on OpenBSD (outputwith this patch):

```
# ktrace ./yggdrasil -autoconf | grep Interface
2024/09/24 17:26:29 Interface name: tun0
2024/09/24 17:26:29 Interface IPv6: 201:26e:68f0:502e:f445:13eb:2fe1:f7cd/7
2024/09/24 17:26:29 Interface MTU: 16384
```

```
$ ifconfig tun0 | head -n1
tun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 16384
```

```
# kdump | grep ioctl
 53097 yggdrasil CALL  ioctl(10,SIOCGIFMTU,0xc0000376b8)
 53097 yggdrasil RET   ioctl 0
 53097 yggdrasil CALL  ioctl(10,SIOCSIFMTU,0xc0000376c0)
 53097 yggdrasil RET   ioctl 0
 53097 yggdrasil CALL  ioctl(10,SIOCGIFMTU,0xc0000377f8)
 53097 yggdrasil RET   ioctl 0
 53097 yggdrasil CALL  ioctl(10,_IOW('i',12,0x20),0xc00003777c)
 53097 yggdrasil RET   ioctl -1 errno 25 Inappropriate ioctl for device
       "2024/09/24 17:26:29 Error in SIOCSIFADDR_IN6: inappropriate ioctl for device
```

(The completely broken address ioctl is another story...)
2024-09-29 21:05:38 +01:00
Neil Alexander
c00779c7d3
Multicast interface detection and shutdown tweaks
May help with #1173.
2024-09-29 20:58:10 +01:00
Arceliar
43a1a3de64 update ironwood dependency 2024-09-28 18:52:04 -05:00
Neil Alexander
b8ab843a98
Update admin socket response sorting 2024-09-23 22:40:52 +01:00
Neil Alexander
e138fa679c
Fix link panic when shutting down (closes #1168) 2024-09-22 17:05:25 +01:00
Neil Alexander
361b9fd6fc
Update WebSocket dependency to new import path 2024-09-22 16:54:58 +01:00
Neil Alexander
5461bb380e
Update dependencies 2024-09-22 16:51:04 +01:00
cathugger
34f087de1c
argument to change uid/gid (#927)
different from
https://github.com/yggdrasil-network/yggdrasil-go/pull/817 in that it
can resolve user names, automatically use user's primary gid & allows
specifying gid in the same argument, with `:` eg `username:groupname`.
feel free to criticize & suggest different argument name & description
because i didn't put much of thought to that.

---------

Co-authored-by: Neil <git@neilalexander.dev>
Co-authored-by: VNAT <xepjk@protonmail.com>
Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2024-09-22 15:46:54 +00:00
Neil
c4b29b735c
Link costing based on average RTT (#1171)
This PR updates Ironwood to include the new RTT-based link costing and
updates `yggdrasilctl` to report the cost in `getPeers`.

Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2024-09-21 22:05:23 +00:00
Sergey Bobrenok
947b6ad7aa
Restore local peer discovery mechanism on Android 11+ (#1158)
This solution is bases on https://github.com/wlynxg/anet project.
`github.com/wlynxg/anet` is a partial alternative implementation of the
`golang.org/x/net` module. The goal of `anet` module is to provide
workarounds of the issues https://github.com/golang/go/issues/40569 and
https://github.com/golang/go/issues/68082 on Android 11+.

Tested on AOSP 13.

Resolves: #1149
2024-08-16 18:28:57 +01:00
Neil Alexander
340cedbe14
Yggdrasil 0.5.8 2024-08-12 19:17:40 +01:00
Neil Alexander
b1283e15f6
Link state tracking tweaks and improved shutdown 2024-08-11 10:42:25 +01:00
Neil Alexander
ef989bef63
Multicast module state tweaks 2024-08-11 10:41:58 +01:00
Neil Alexander
af9ff34995
Fix macOS build 2024-08-07 19:55:10 +01:00
Neil Alexander
63cd757525
Remove waitForTUNUp from TUN
Causes issues such as #1156.
2024-08-07 19:52:19 +01:00
Revertron
5e5de3a343
Fixed wait for TUN to come up (#1157)
So, the function waiting for TUN to come up never succeeds:
```
func waitForTUNUp(ch <-chan wgtun.Event) bool {
	t := time.After(time.Second * 5)
	for {
		select {
		case ev := <-ch:
			if ev == wgtun.EventUp {
				return true
			}
		case <-t:
			return false
		}
	}
}
```
I've tried the sleep for one second, and it works flawlessly on several
PCs.

Another point - sometimes, if the service stop abruptly (in case of some
errors) there is an old hidden device in the system, that we need to
uninstall, and then create new.
2024-08-06 10:28:15 +01:00
Neil Alexander
edf179ed26
Yggdrasil 0.5.7 2024-08-05 19:18:38 +01:00
Neil Alexander
9950d1225d
Improve link and handshake errors 2024-08-01 21:53:48 +01:00
Revertron
4fbdeb4e3f
Fixed Windows service life-cycle. (#1153)
This fix fixes two issues:
https://github.com/yggdrasil-network/yggdrasil-go/issues/993 &
https://github.com/yggdrasil-network/yggdrasil-go/issues/1098
2024-07-25 13:55:14 +01:00
Vasyl Gello
5ea16e63a1
Implement websocket (ws:// and wss://) links (#1152)
ws:// can be listened and dialed
wss:// is a convenience link for ws:// that supports dialing to ws://
peer.

---------

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2024-07-23 22:58:11 +01:00
Neil Alexander
da7ebde828
Update dependencies 2024-07-20 15:37:31 +01:00
Neil
02d92ff81c
TUN vectorised reads/writes (#1145)
This PR updates the Wireguard dependency and updates to use new
vectorised reads/writes, which should reduce the number of syscalls and
improve performance.

This will only make a difference on Linux as this is the only platform
for which the Wireguard TUN library supports vectorised reads/writes.
For other platforms, single reads and writes will be performed as usual.

---------

Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2024-07-20 15:24:30 +01:00
Neil Alexander
04c0acf71b
Various clean-ups 2024-07-20 12:31:58 +01:00
Neil Alexander
8ecc402d7c
Allow multiple connections to the same link-local address
Note that this may mean that currently we end up with two links to each multicast-discovered peer, one incoming and one outgoing
2024-07-20 11:31:08 +01:00
Neil Alexander
c505097be0
Update mobile build for iOS/macOS framework generation 2024-06-26 23:17:11 +01:00
Neil
fec96a38a4
Release: Yggdrasil v0.5.6 (#1144)
* Changelog updates for Yggdrasil v0.5.6

* Fix spelling error

---------

Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
Co-authored-by: Arceliar <Arceliar@users.noreply.github.com>
2024-05-30 23:30:05 +01:00
Neil
f788a18bef
Measure RTT, report in getPeers (#1143)
Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2024-05-30 22:46:06 +01:00
Neil Alexander
fcefb20993
Fix interval check when sending multicast beacons 2024-05-28 10:03:48 +01:00
Neil Alexander
2831d73f73
Try to fix WiX for Windows MSI builds 2024-05-27 22:52:48 +01:00
Neil Alexander
c2811c0cdc
Update more GHA actions due to deprecations 2024-05-27 22:14:28 +01:00
Neil Alexander
5d9c5b3c9b
Minimum Go 1.21, update quic-go, update some CI actions 2024-05-27 22:03:41 +01:00
Paul Donald
f56f9c124c
Minor Fixes (#1107)
* Minor comment fixes.

* Optimize PeerEntry for memory efficiency

* Improve NodeConfig for memory alignment
2024-05-27 21:57:28 +01:00
trashpile-shenanigans
5da1fbe397
Bump minimum required go version to 1.20 in documentation as required by quic-go dependency (#1138) 2024-05-27 21:53:52 +01:00
Arceliar
6f3a0a71d4 update ironwood and other dependencies 2024-05-25 06:16:11 -05:00
Arceliar
6cbe56adfe fix incorrect pool use 2024-05-25 06:15:36 -05:00
Arceliar
2d644eabc3 update ironwood (updates bloom dependency) 2024-03-21 21:33:07 -05:00
Neil Alexander
2c20a04369
Release: Yggdrasil 0.5.5 2024-01-27 22:54:54 +00:00
Neil Alexander
81f2c711b4
Fix panic in getPeers on abstract UNIX socket names
Fixes #1111
2024-01-15 23:14:43 +00:00
Neil
180d7bf499
Adjust default backoff max to just over 1 hour, add ?maxbackoff= peer option (#1124)
Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
2024-01-15 23:09:07 +00:00
Neil Alexander
9f4c89acad
Update dependencies 2024-01-15 23:00:58 +00:00
Neil Alexander
5da4c1131e
Update ironwood to ddd1fa6 2024-01-15 19:07:17 +00:00
Neil Alexander
768278a8e6
Improve getPeers sorting 2024-01-11 22:37:05 +00:00
Neil Alexander
1e9a59edf9
Update behaviour in QUIC listener handler 2024-01-05 11:45:20 +00:00
Neil Alexander
3dfa6d0cc9
Validate public key lengths on debug_ API endpoints (fixes #1113) 2023-12-03 17:55:12 +00:00
Neil Alexander
6b6cd0bed5
Fix PPROFLISTEN 2023-11-28 13:24:54 +00:00
Neil Alexander
3d15da34ad
Release: Yggdrasil 0.5.4 2023-11-27 14:17:16 +00:00
Arceliar
741f825b8e update ironwood dependency, should fix bloom filter encoding crash 2023-11-27 07:18:16 -06:00
Neil Alexander
676ae52503
Release: Yggdrasil 0.5.3 2023-11-26 18:42:08 +00:00
Neil Alexander
fef553ed18
Tweak logging 2023-11-26 16:28:48 +00:00
Neil Alexander
f6f669617f
Fix -normaliseconf when using PrivateKeyPath 2023-11-26 16:20:52 +00:00
Neil Alexander
39c4b24395
Don't use 0-RTT for QUIC 2023-11-26 16:19:00 +00:00
Arceliar
0d676c6a3b update ironwood dependency 2023-11-26 04:56:44 -06:00
Neil Alexander
a0b3897278
Cap link backoff at roughly 4.5 hours 2023-11-21 23:54:27 +00:00
Arceliar
abec2256ae
Merge pull request #1105 from yggdrasil-network/neil/backoff
Tweak backoff success handling
2023-11-21 04:49:41 -06:00
Neil Alexander
7aca869170
Tweak backoff success handling 2023-11-21 10:35:17 +00:00
Arceliar
b759683b76 Merge branch 'develop' of https://github.com/yggdrasil-network/yggdrasil-go into develop 2023-11-09 22:06:38 -06:00
Arceliar
6677d70648 update ironwood, fixed data race from buffered pathfinder traffic 2023-11-09 22:06:19 -06:00
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
66 changed files with 2039 additions and 710 deletions

View file

@ -15,12 +15,12 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
- uses: actions/setup-go@v5
with:
go-version: 1.21
- uses: actions/checkout@v3
go-version: stable
- uses: actions/checkout@v4
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v6
with:
args: --issues-exit-code=1
@ -34,7 +34,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
@ -51,17 +51,17 @@ jobs:
strategy:
fail-fast: false
matrix:
goversion: ["1.20", "1.21"]
goversion: ["1.22", "1.23", "1.24"]
name: Build & Test (Linux, Go ${{ matrix.goversion }})
needs: [lint]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.goversion }}
@ -75,17 +75,17 @@ jobs:
strategy:
fail-fast: false
matrix:
goversion: ["1.20", "1.21"]
goversion: ["1.22", "1.23", "1.24"]
name: Build & Test (Windows, Go ${{ matrix.goversion }})
needs: [lint]
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.goversion }}
@ -99,17 +99,17 @@ jobs:
strategy:
fail-fast: false
matrix:
goversion: ["1.20", "1.21"]
goversion: ["1.22", "1.23", "1.24"]
name: Build & Test (macOS, Go ${{ matrix.goversion }})
needs: [lint]
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.goversion }}
@ -119,6 +119,32 @@ jobs:
- name: Unit tests
run: go test -v ./...
build-freebsd:
strategy:
fail-fast: false
matrix:
goversion: ["1.22", "1.23", "1.24"]
goos:
- freebsd
- openbsd
name: Build (Cross ${{ matrix.goos }}, Go ${{ matrix.goversion }})
needs: [lint]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.goversion }}
- name: Build Yggdrasil
run: go build -v ./...
env:
GOOS: ${{ matrix.goos }}
tests-ok:
name: All tests passed
needs: [lint, codeql, build-linux, build-windows, build-macos]

View file

@ -16,16 +16,16 @@ jobs:
name: Package (Debian, ${{ matrix.pkgarch }})
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: "1.20"
go-version: "stable"
- name: Build package
env:
@ -33,7 +33,7 @@ jobs:
run: sh contrib/deb/generate.sh
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Debian package (${{ matrix.pkgarch }})
path: "*.deb"
@ -49,14 +49,14 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: "1.20"
go-version: "stable"
- name: Build package
env:
@ -64,7 +64,7 @@ jobs:
run: sh contrib/macos/create-pkg.sh
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: macOS package (${{ matrix.pkgarch }})
path: "*.pkg"
@ -80,20 +80,23 @@ jobs:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: "1.20"
go-version: "stable"
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v4
- name: Build package
run: sh contrib/msi/build-msi.sh ${{ matrix.pkgarch }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Windows package (${{ matrix.pkgarch }})
path: "*.msi"
@ -107,22 +110,22 @@ jobs:
name: Package (Router, ${{ matrix.pkgarch }})
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
path: yggdrasil
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
repository: neilalexander/vyatta-yggdrasil
path: vyatta-yggdrasil
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: "1.20"
go-version: "stable"
- name: Build package
env:
@ -130,7 +133,7 @@ jobs:
run: cd /home/runner/work/yggdrasil-go/yggdrasil-go/vyatta-yggdrasil && ./build-${{ matrix.pkgarch }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Router package (${{ matrix.pkgarch }})
path: "/home/runner/work/yggdrasil-go/yggdrasil-go/vyatta-yggdrasil/*.deb"

View file

@ -2,9 +2,10 @@ run:
build-tags:
- lint
issues-exit-code: 0 # TODO: change this to 1 when we want it to fail builds
skip-dirs:
issues:
exclude-dirs:
- contrib/
- misc/
linters:
disable:
- gocyclo
- gocyclo

View file

@ -26,22 +26,196 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- in case of vulnerabilities.
-->
## [0.5.0] - Release Candidate 3
## [0.5.12] - 2024-12-18
* Go 1.22 is now required to build Yggdrasil
### Changed
* The `latency_ms` field in the admin socket `getPeers` response has been renamed to `latency`
### Fixed
* Restored `removePeer` admin socket endpoint
* Fixed the `RetryPeersNow` API call for mobile
* A timing regression which causes a higher level of idle protocol traffic on each peering has been fixed
* The `-user` flag now correctly detects an empty user/group specification
## [0.5.0] - Release Candidate 2
## [0.5.11] - 2024-12-12
### Added
* Support for `unveil` and `pledge` on OpenBSD
### Changed
* The parent selection algorithm now only chooses a new parent if there is a larger cost benefit to doing so, which should help to stabilise the tree
* The bloom filters are now repropagated periodically, to avoid nodes getting stuck with bad state
### Fixed
* A bug which could result in high CPU usage after a network interface change has been fixed
* TLS listeners no longer require a TLS client certificate, as it is not necessary
* A panic in the mobile wrapper has been fixed when getting peers JSON
* A memory leak caused by missed cleanup of the peer response map has been fixed
* Other bug fixes with bloom filter propagation for off-tree filters and zero vs one bits
* TLS-based peering connections now support TLS 1.2 again
## [0.5.0] - Release Candidate 1
## [0.5.10] - 2024-11-24
### Added
* The `getPeers` admin endpoint will now report the current transmit/receive rate for each given peer
* The `getMulticastInterfaces` admin endpoint now reports much more useful information about each interface, rather than just a list of interface names
### Changed
* Minor tweaks to the routing algorithm:
* The next-hop selection will now prefer shorter paths when the costed distance is otherwise equal, tiebreaking on peering uptime to fall back to more stable paths
* Link cost calculations have been smoothed out, making the costs less sensitive to sudden spikes in latency
* Reusable name lookup and peer connection logic across different peering types for more consistent behaviour
* Some comments in the configuration file have been revised for clarity
* Upgrade dependencies
### Fixed
* Nodes with `IfName` set to `none` will now correctly respond to debug RPC requests
* The admin socket will now be created reliably before dropping privileges with `-user`
* Clear supplementary groups when providing a group ID as well as a user ID to `-user`
* SOCKS and WebSocket peerings should now use the correct source interface when specified in `InterfacePeers`
* `Peers` and `InterfacePeers` addresses that are obviously invalid (such as unspecified or multicast addresses) will now be correctly ignored
* Listeners should now shut down correctly, which should resolve issues where multicast listeners for specific interfaces would not come back up or would log errors
## [0.5.9] - 2024-10-19
### Added
* New command line option `-user` for changing the process UID/GID
### Changed
* The routing algorithm has been updated with RTT-aware link costing, which should prefer lower latency links over higher latency links where possible
* The calculated cost is an average of the link RTT, but newly established links are costed higher to begin with, such that unstable peerings can be avoided
* Link costs are only used where multiple next-hops are available and will be ignored if there is only one loop-free path to the destination
* This is protocol-compatible with existing v0.5.x nodes but will have the best results when peering with nodes that are also running the latest version
* The `getPeers` endpoint will now report the calculated link cost for each given peer
* Upgrade dependencies
### Fixed
* Multicast discovery should now work again when building Yggdrasil as an Android framework
* Multicast discovery will now correctly ignore interfaces that are not marked as running
* Ephemeral links, such as those added by multicast, will no longer try to reconnect in a fast loop, fixing a high CPU issue
* The TUN interface will no longer stop working when hitting a segment read error from vectorised reads
* The `AllowedPublicKeys` option will once again no longer apply to multicast peerings, as was originally intended
* A potential panic when shutting down peering links has been fixed
* A redundant system call for setting MTU on OpenBSD has been removed
## [0.5.8] - 2024-08-12
### Fixed
* A bug which caused startup problems on Windows and FreeBSD should be fixed
* Resolved some minor link state and listener management bugs during shutdown
## [0.5.7] - 2024-08-05
### Added
* WebSocket support for peerings, by using the new `ws://` scheme in `Listen` and `Peers`
* Additionally, the `wss://` scheme can be used to connect to a WebSocket peer behind a HTTPS reverse proxy
### Changed
* On Linux, the TUN adapter now uses vectorised reads/writes where possible, which should reduce the amount of CPU time spent on syscalls and potentially improve throughput
* Link error handling has been improved and various link error messages have been rewritten to be clearer
* Upgrade dependencies
### Fixed
* Multiple multicast connections to the same remote machine should now work correctly
* You may get two connections in some cases, one inbound and one outbound, this is known and will not cause problems
* Running as a Windows service should be more reliable with service startup and shutdown bugs fixed
## [0.5.6] - 2024-05-30
* Go 1.21 is now required to build Yggdrasil
### Added
* The `getPeers` endpoint now reports the RTT/latency of directly connected peers
### Changed
* The tree parent selection algorithm now prefers the lowest latency peers instead of the most stable
* Session key exchange logic has been changed to improve throughput and reduce occasional jitter
### Fixed
* Bloom filter hashing now works correctly on big-endian architectures
* Incorrect buffer pool usage has been fixed, reducing memory allocations
* The multicast beacon interval now backs off correctly, reducing the number of beacons sent
* A denial-of-service vulnerability in the QUIC library has been fixed with a dependency update
## [0.5.5] - 2024-01-27
### Added
* A new peer option `?maxbackoff=X` has been added to control the maximum backoff time for a given peer, supports duration values like `5m`, `1h` etc
### Changed
* The maximum backoff period for failing peer connections has been reduced to just over 1 hour, compared to 4.5 hours before
* The `getPeers` endpoint now sorts peers in a more stable fashion
* Upgrade dependencies
### Fixed
* A bug where QUIC listeners could stop listening for incoming connections unexpectedly has been fixed
* The priority tiebreak between multiple peerings to the same node has been fixed
* Peer connection ordering is no longer sensitive to poor system time resolution
* The admin socket now verifies the length of input public keys
* The `PPROFLISTEN` environment variable has been fixed and now starts the pprof listener correctly
* A panic in `getPeers` has been fixed when using abstract UNIX sockets on Linux
## [0.5.4] - 2023-11-27
### Fixed
* Fixed a crash that could happen when calculating the size of bloom filters during encoding
## [0.5.3] - 2023-11-26
### Fixed
* Fixed a data race from buffered pathfinder traffic
* Fix a bug where the next-hop selection may not take shortcuts through treespace
* Backoffs are now reset correctly when a successful handshake is completed
* Backoffs will no longer exceed roughly 4.5 hours when peers are down for a long time
* The `-normaliseconf` option will now work correctly with `PrivateKeyPath`
* Improved the reliability of QUIC peering setup by disabling 0-RTT
## [0.5.2] - 2023-11-06
### Added
* New `-publickey` command line option that prints the derived public key from a configuration file
* Support for connecting to TLS peers via SOCKS with the new `sockstls://` link schema
### Changed
* Stabilise tree parent selection algorithm
* Improved logging when the TUN interface fails to set up
### Fixed
* Fixed a panic that could occur when a connection reaches an inconsistent error state
* The admin socket will now report more peering handshake error conditions in `getPeers`
* Yggdrasil will no longer panic at startup when duplicate peers are configured
* The `build` script will no longer incorrectly import `LDFLAGS` from the environment
## [0.5.1] - 2023-10-28
### Fixed
* Fix the Debian package so that upgrades are handled more smoothly
## [0.5.0] - 2023-10-28
### Added
@ -141,7 +315,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Changed
* Go 1.17 is now required to build Yggdrasil
* Go 1.20 is now required to build Yggdrasil
## [0.4.3] - 2022-02-06

View file

@ -24,7 +24,7 @@ or tools in the `contrib` folder.
If you want to build from source, as opposed to installing one of the pre-built
packages:
1. Install [Go](https://golang.org) (requires Go 1.17 or later)
1. Install [Go](https://golang.org) (requires Go 1.22 or later)
2. Clone this repository
2. Run `./build`

View file

@ -18,18 +18,27 @@ import (
"runtime"
"time"
"suah.dev/protect"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
type keySet struct {
priv ed25519.PrivateKey
pub ed25519.PublicKey
count uint64
}
func main() {
if err := protect.Pledge("stdio"); err != nil {
panic(err)
}
threads := runtime.GOMAXPROCS(0)
fmt.Println("Threads:", threads)
start := time.Now()
var totalKeys uint64
totalKeys = 0
var currentBest ed25519.PublicKey
newKeys := make(chan keySet, threads)
for i := 0; i < threads; i++ {
@ -38,8 +47,9 @@ func main() {
for {
newKey := <-newKeys
if isBetter(currentBest, newKey.pub) || len(currentBest) == 0 {
totalKeys += newKey.count
currentBest = newKey.pub
fmt.Println("-----", time.Since(start))
fmt.Println("-----", time.Since(start), "---", totalKeys, "keys tried")
fmt.Println("Priv:", hex.EncodeToString(newKey.priv))
fmt.Println("Pub:", hex.EncodeToString(newKey.pub))
addr := address.AddrForKey(newKey.pub)
@ -62,11 +72,14 @@ func isBetter(oldPub, newPub ed25519.PublicKey) bool {
func doKeys(out chan<- keySet) {
bestKey := make(ed25519.PublicKey, ed25519.PublicKeySize)
var count uint64
count = 0
for idx := range bestKey {
bestKey[idx] = 0xff
}
for {
pub, priv, err := ed25519.GenerateKey(nil)
count++
if err != nil {
panic(err)
}
@ -74,6 +87,7 @@ func doKeys(out chan<- keySet) {
continue
}
bestKey = pub
out <- keySet{priv, pub}
out <- keySet{priv, pub, count}
count = 0
}
}

View file

@ -0,0 +1,10 @@
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
package main
import "errors"
func chuser(user string) error {
return errors.New("setting uid/gid is not supported on this platform")
}

View file

@ -0,0 +1,63 @@
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
package main
import (
"fmt"
"os/user"
"strconv"
"strings"
"golang.org/x/sys/unix"
)
func chuser(input string) error {
givenUser, givenGroup, _ := strings.Cut(input, ":")
if givenUser == "" {
return fmt.Errorf("user is empty")
}
if strings.Contains(input, ":") && givenGroup == "" {
return fmt.Errorf("group is empty")
}
var (
err error
usr *user.User
grp *user.Group
uid, gid int
)
if usr, err = user.LookupId(givenUser); err != nil {
if usr, err = user.Lookup(givenUser); err != nil {
return err
}
}
if uid, err = strconv.Atoi(usr.Uid); err != nil {
return err
}
if givenGroup != "" {
if grp, err = user.LookupGroupId(givenGroup); err != nil {
if grp, err = user.LookupGroup(givenGroup); err != nil {
return err
}
}
gid, _ = strconv.Atoi(grp.Gid)
} else {
gid, _ = strconv.Atoi(usr.Gid)
}
if err := unix.Setgroups([]int{gid}); err != nil {
return fmt.Errorf("setgroups: %d: %v", gid, err)
}
if err := unix.Setgid(gid); err != nil {
return fmt.Errorf("setgid: %d: %v", gid, err)
}
if err := unix.Setuid(uid); err != nil {
return fmt.Errorf("setuid: %d: %v", uid, err)
}
return nil
}

View file

@ -0,0 +1,80 @@
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
package main
import (
"os/user"
"testing"
)
// Usernames must not contain a number sign.
func TestEmptyString(t *testing.T) {
if chuser("") == nil {
t.Fatal("the empty string is not a valid user")
}
}
// Either omit delimiter and group, or omit both.
func TestEmptyGroup(t *testing.T) {
if chuser("0:") == nil {
t.Fatal("the empty group is not allowed")
}
}
// Either user only or user and group.
func TestGroupOnly(t *testing.T) {
if chuser(":0") == nil {
t.Fatal("group only is not allowed")
}
}
// Usenames must not contain the number sign.
func TestInvalidUsername(t *testing.T) {
const username = "#user"
if chuser(username) == nil {
t.Fatalf("'%s' is not a valid username", username)
}
}
// User IDs must be non-negative.
func TestInvalidUserid(t *testing.T) {
if chuser("-1") == nil {
t.Fatal("User ID cannot be negative")
}
}
// Change to the current user by ID.
func TestCurrentUserid(t *testing.T) {
usr, err := user.Current()
if err != nil {
t.Fatal(err)
}
if usr.Uid != "0" {
t.Skip("setgroups(2): Only the superuser may set new groups.")
}
if err = chuser(usr.Uid); err != nil {
t.Fatal(err)
}
}
// Change to a common user by name.
func TestCommonUsername(t *testing.T) {
usr, err := user.Current()
if err != nil {
t.Fatal(err)
}
if usr.Uid != "0" {
t.Skip("setgroups(2): Only the superuser may set new groups.")
}
if err := chuser("nobody"); err != nil {
if _, ok := err.(user.UnknownUserError); ok {
t.Skip(err)
}
t.Fatal(err)
}
}

View file

@ -14,6 +14,8 @@ import (
"strings"
"syscall"
"suah.dev/protect"
"github.com/gologme/log"
gsyslog "github.com/hashicorp/go-syslog"
"github.com/hjson/hjson-go/v4"
@ -39,6 +41,20 @@ type node struct {
// The main function is responsible for configuring and starting Yggdrasil.
func main() {
// Not all operations are coverable with pledge(2), so immediately
// limit file system access with unveil(2), effectively preventing
// "proc exec" promises right from the start:
//
// - read arbitrary config file
// - create/write arbitrary log file
// - read/write/chmod/remove admin socket, if at all
if err := protect.Unveil("/", "rwc"); err != nil {
panic(fmt.Sprintf("unveil: / rwc: %v", err))
}
if err := protect.UnveilBlock(); err != nil {
panic(fmt.Sprintf("unveil: %v", err))
}
genconf := flag.Bool("genconf", false, "print a new config to stdout")
useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin")
useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path")
@ -48,17 +64,19 @@ func main() {
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")
chuserto := flag.String("user", "", "user (and, optionally, group) to set UID/GID to")
flag.Parse()
done := make(chan struct{})
defer close(done)
// Catch interrupts from the operating system to exit gracefully.
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
// Capture the service being stopped on Windows.
minwinsvc.SetOnExit(cancel)
// Create a new logger that logs output to stdout.
var logger *log.Logger
switch *logto {
@ -113,6 +131,7 @@ func main() {
_ = f.Close()
case *genconf:
cfg.AdminListen = ""
var bs []byte
if *confjson {
bs, err = json.MarshalIndent(cfg, "", " ")
@ -154,7 +173,15 @@ func main() {
fmt.Println(ipnet.String())
return
case *getpkey:
fmt.Println(hex.EncodeToString(publicKey))
return
case *normaliseconf:
cfg.AdminListen = ""
if cfg.PrivateKeyPath != "" {
cfg.PrivateKey = nil
}
var bs []byte
if *confjson {
bs, err = json.MarshalIndent(cfg, "", " ")
@ -178,11 +205,18 @@ func main() {
n := &node{}
// Setup the Yggdrasil node itself.
// Set up the Yggdrasil node itself.
{
iprange := net.IPNet{
IP: net.ParseIP("200::"),
Mask: net.CIDRMask(7, 128),
}
options := []core.SetupOption{
core.NodeInfo(cfg.NodeInfo),
core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
core.PeerFilter(func(ip net.IP) bool {
return !iprange.Contains(ip)
}),
}
for _, addr := range cfg.Listen {
options = append(options, core.ListenAddress(addr))
@ -206,16 +240,19 @@ func main() {
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())
logger.Printf("Your public key is %s", hex.EncodeToString(n.core.PublicKey()))
logger.Printf("Your IPv6 address is %s", address.String())
logger.Printf("Your IPv6 subnet is %s", subnet.String())
}
// Setup the admin socket.
// Set up the admin socket.
{
options := []admin.SetupOption{
admin.ListenAddress(cfg.AdminListen),
}
if cfg.LogLookups {
options = append(options, admin.LogLookups{})
}
if n.admin, err = admin.New(n.core, logger, options...); err != nil {
panic(err)
}
@ -224,7 +261,7 @@ func main() {
}
}
// Setup the multicast module.
// Set up the multicast module.
{
options := []multicast.SetupOption{}
for _, intf := range cfg.MulticastInterfaces {
@ -245,7 +282,7 @@ func main() {
}
}
// Setup the TUN module.
// Set up the TUN module.
{
options := []tun.SetupOption{
tun.InterfaceName(cfg.IfName),
@ -259,6 +296,37 @@ func main() {
}
}
//Windows service shutdown
minwinsvc.SetOnExit(func() {
logger.Infof("Shutting down service ...")
cancel()
// Wait for all parts to shutdown properly
<-done
})
// Change user if requested
if *chuserto != "" {
err = chuser(*chuserto)
if err != nil {
panic(err)
}
}
// Promise final modes of operation. At this point, if at all:
// - raw socket is created/open
// - admin socket is created/open
// - privileges are dropped to non-root user
//
// Peers, InterfacePeers, Listen can be UNIX sockets;
// Go's net.Listen.Close() deletes files on shutdown.
promises := []string{"stdio", "cpath", "inet", "unix", "dns"}
if len(cfg.MulticastInterfaces) > 0 {
promises = append(promises, "mcast")
}
if err := protect.Pledge(strings.Join(promises, " ")); err != nil {
panic(fmt.Sprintf("pledge: %v: %v", promises, err))
}
// Block until we are told to shut down.
<-ctx.Done()

View file

@ -38,9 +38,8 @@ func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() {
fmt.Println("Examples:")
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")

View file

@ -13,6 +13,8 @@ import (
"strings"
"time"
"suah.dev/protect"
"github.com/olekukonko/tablewriter"
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
"github.com/yggdrasil-network/yggdrasil-go/src/core"
@ -22,6 +24,11 @@ import (
)
func main() {
// read config, speak DNS/TCP and/or over a UNIX socket
if err := protect.Pledge("stdio rpath inet unix dns"); err != nil {
panic(err)
}
// makes sure we can use defer and still return an error code to the OS
os.Exit(run())
}
@ -78,6 +85,11 @@ func run() int {
panic(err)
}
// config and socket are done, work without unprivileges
if err := protect.Pledge("stdio"); err != nil {
panic(err)
}
logger.Println("Connected")
defer conn.Close()
@ -174,29 +186,41 @@ func run() int {
if err := json.Unmarshal(recv.Response, &resp); err != nil {
panic(err)
}
table.SetHeader([]string{"URI", "State", "Dir", "IP Address", "Uptime", "RX", "TX", "Pr", "Last Error"})
table.SetHeader([]string{"URI", "State", "Dir", "IP Address", "Uptime", "RTT", "RX", "TX", "Down", "Up", "Pr", "Cost", "Last Error"})
for _, peer := range resp.Peers {
state, lasterr, dir := "Up", "-", "Out"
state, lasterr, dir, rtt, rxr, txr := "Up", "-", "Out", "-", "-", "-"
if !peer.Up {
state, lasterr = "Down", fmt.Sprintf("%s ago: %s", peer.LastErrorTime.Round(time.Second), peer.LastError)
} else if rttms := float64(peer.Latency.Microseconds()) / 1000; rttms > 0 {
rtt = fmt.Sprintf("%.02fms", rttms)
}
if peer.Inbound {
dir = "In"
}
uri, err := url.Parse(peer.URI)
if err != nil {
panic(err)
uristring := peer.URI
if uri, err := url.Parse(peer.URI); err == nil {
uri.RawQuery = ""
uristring = uri.String()
}
if peer.RXRate > 0 {
rxr = peer.RXRate.String() + "/s"
}
if peer.TXRate > 0 {
txr = peer.TXRate.String() + "/s"
}
uri.RawQuery = ""
table.Append([]string{
uri.String(),
uristring,
state,
dir,
peer.IPAddress,
(time.Duration(peer.Uptime) * time.Second).String(),
rtt,
peer.RXBytes.String(),
peer.TXBytes.String(),
rxr,
txr,
fmt.Sprintf("%d", peer.Priority),
fmt.Sprintf("%d", peer.Cost),
lasterr,
})
}
@ -269,9 +293,21 @@ func run() int {
if err := json.Unmarshal(recv.Response, &resp); err != nil {
panic(err)
}
table.SetHeader([]string{"Interface"})
fmtBool := func(b bool) string {
if b {
return "Yes"
}
return "-"
}
table.SetHeader([]string{"Name", "Listen Address", "Beacon", "Listen", "Password"})
for _, p := range resp.Interfaces {
table.Append([]string{p})
table.Append([]string{
p.Name,
p.Address,
fmtBool(p.Beacon),
fmtBool(p.Listen),
fmtBool(p.Password),
})
}
table.Render()

BIN
contrib/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,11 @@
# Last Modified: Mon Feb 3 22:19:45 2025
include <tunables/global>
/usr/bin/yggdrasilctl {
include <abstractions/base>
/etc/yggdrasil.conf rw,
/run/yggdrasil.sock rw,
owner /sys/kernel/mm/transparent_hugepage/hpage_pmd_size r,
}

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/
@ -47,11 +50,12 @@ echo 9 > /tmp/$PKGNAME/debian/compat
cat > /tmp/$PKGNAME/debian/control << EOF
Package: $PKGNAME
Version: $PKGVERSION
Section: contrib/net
Priority: extra
Section: golang
Priority: optional
Architecture: $PKGARCH
Replaces: $PKGREPLACES
Conflicts: $PKGREPLACES
Depends: systemd
Maintainer: Neil Alexander <neilalexander@users.noreply.github.com>
Description: Yggdrasil Network
Yggdrasil is an early-stage implementation of a fully end-to-end encrypted IPv6
@ -68,35 +72,52 @@ EOF
cat > /tmp/$PKGNAME/debian/install << EOF
usr/bin/yggdrasil usr/bin
usr/bin/yggdrasilctl usr/bin
etc/systemd/system/*.service etc/systemd/system
lib/systemd/system/*.service lib/systemd/system
EOF
cat > /tmp/$PKGNAME/debian/postinst << EOF
#!/bin/sh
systemctl daemon-reload
if ! getent group yggdrasil 2>&1 > /dev/null; then
groupadd --system --force yggdrasil || echo "Failed to create group 'yggdrasil' - please create it manually and reinstall"
groupadd --system --force yggdrasil
fi
if [ -f /etc/yggdrasil.conf ];
if [ ! -d /etc/yggdrasil ];
then
mkdir -p /etc/yggdrasil
chown root:yggdrasil /etc/yggdrasil
chmod 750 /etc/yggdrasil
fi
if [ ! -f /etc/yggdrasil/yggdrasil.conf ];
then
test -f /etc/yggdrasil.conf && mv /etc/yggdrasil.conf /etc/yggdrasil/yggdrasil.conf
fi
if [ -f /etc/yggdrasil/yggdrasil.conf ];
then
mkdir -p /var/backups
echo "Backing up configuration file to /var/backups/yggdrasil.conf.`date +%Y%m%d`"
cp /etc/yggdrasil.conf /var/backups/yggdrasil.conf.`date +%Y%m%d`
echo "Normalising and updating /etc/yggdrasil.conf"
/usr/bin/yggdrasil -useconf -normaliseconf < /var/backups/yggdrasil.conf.`date +%Y%m%d` > /etc/yggdrasil.conf
chgrp yggdrasil /etc/yggdrasil.conf
cp /etc/yggdrasil/yggdrasil.conf /var/backups/yggdrasil.conf.`date +%Y%m%d`
if command -v systemctl >/dev/null; then
systemctl daemon-reload >/dev/null || true
systemctl enable yggdrasil || true
systemctl start yggdrasil || true
fi
echo "Normalising and updating /etc/yggdrasil/yggdrasil.conf"
/usr/bin/yggdrasil -useconf -normaliseconf < /var/backups/yggdrasil.conf.`date +%Y%m%d` > /etc/yggdrasil/yggdrasil.conf
chown root:yggdrasil /etc/yggdrasil/yggdrasil.conf
chmod 640 /etc/yggdrasil/yggdrasil.conf
else
echo "Generating initial configuration file /etc/yggdrasil.conf"
echo "Please familiarise yourself with this file before starting Yggdrasil"
sh -c 'umask 0027 && /usr/bin/yggdrasil -genconf > /etc/yggdrasil.conf'
chgrp yggdrasil /etc/yggdrasil.conf
echo "Generating initial configuration file /etc/yggdrasil/yggdrasil.conf"
/usr/bin/yggdrasil -genconf > /etc/yggdrasil/yggdrasil.conf
chown root:yggdrasil /etc/yggdrasil/yggdrasil.conf
chmod 640 /etc/yggdrasil/yggdrasil.conf
fi
systemctl enable yggdrasil
systemctl restart yggdrasil
exit 0
EOF
cat > /tmp/$PKGNAME/debian/prerm << EOF
#!/bin/sh
@ -110,13 +131,14 @@ EOF
cp yggdrasil /tmp/$PKGNAME/usr/bin/
cp yggdrasilctl /tmp/$PKGNAME/usr/bin/
cp contrib/systemd/*.service /tmp/$PKGNAME/etc/systemd/system/
cp contrib/systemd/yggdrasil-default-config.service.debian /tmp/$PKGNAME/lib/systemd/system/yggdrasil-default-config.service
cp contrib/systemd/yggdrasil.service.debian /tmp/$PKGNAME/lib/systemd/system/yggdrasil.service
tar -czvf /tmp/$PKGNAME/data.tar.gz -C /tmp/$PKGNAME/ \
tar --no-xattrs -czvf /tmp/$PKGNAME/data.tar.gz -C /tmp/$PKGNAME/ \
usr/bin/yggdrasil usr/bin/yggdrasilctl \
etc/systemd/system/yggdrasil.service \
etc/systemd/system/yggdrasil-default-config.service
tar -czvf /tmp/$PKGNAME/control.tar.gz -C /tmp/$PKGNAME/debian .
lib/systemd/system/yggdrasil.service \
lib/systemd/system/yggdrasil-default-config.service
tar --no-xattrs -czvf /tmp/$PKGNAME/control.tar.gz -C /tmp/$PKGNAME/debian .
echo 2.0 > /tmp/$PKGNAME/debian-binary
ar -r $PKGFILE \

View file

@ -7,6 +7,7 @@ set -ef
PKGSRC=${PKGSRC:-github.com/yggdrasil-network/yggdrasil-go/src/version}
PKGNAME=${PKGNAME:-$(sh contrib/semver/name.sh)}
PKGVER=${PKGVER:-$(sh contrib/semver/version.sh --bare)}
GOVER=$(go version | { read _ _ version _; echo ${version#go}; })
LDFLAGS="-X $PKGSRC.buildName=$PKGNAME -X $PKGSRC.buildVersion=$PKGVER"
ARGS="-v"
@ -33,6 +34,15 @@ if [ ! $IOS ] && [ ! $ANDROID ]; then
exit 1
fi
ver_le() {
printf "$1\n$2\n" | sort -VC
}
if [ $ANDROID ] && ver_le 1.23.0 $GOVER ; then
# github.com/wlynxg/anet library relies on //go:linkname
LDFLAGS="$LDFLAGS -checklinkname=0"
fi
if [ $IOS ]; then
echo "Building framework for iOS"
go get golang.org/x/mobile/bind

View file

@ -1,6 +1,7 @@
package mobile
import (
"crypto/ed25519"
"encoding/hex"
"encoding/json"
"net"
@ -15,8 +16,6 @@ import (
"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"
)
// Yggdrasil mobile package is meant to "plug the gap" for mobile support, as
@ -48,13 +47,22 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
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
}
// Setup the Yggdrasil node itself.
// Set up the Yggdrasil node itself.
{
options := []core.SetupOption{}
iprange := net.IPNet{
IP: net.ParseIP("200::"),
Mask: net.CIDRMask(7, 128),
}
options := []core.SetupOption{
core.PeerFilter(func(ip net.IP) bool {
return !iprange.Contains(ip)
}),
}
for _, peer := range m.config.Peers {
options = append(options, core.Peer{URI: peer})
}
@ -84,9 +92,10 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
logger.Infof("Your IPv6 subnet is %s", subnet.String())
}
// Setup the multicast module.
// Set up the multicast module.
if len(m.config.MulticastInterfaces) > 0 {
var err error
logger.Infof("Initializing multicast %s", "")
options := []multicast.SetupOption{}
for _, intf := range m.config.MulticastInterfaces {
options = append(options, multicast.MulticastInterface{
@ -98,9 +107,10 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
Password: intf.Password,
})
}
logger.Infof("Starting multicast %s", "")
m.multicast, err = multicast.New(m.core, m.logger, options...)
if err != nil {
m.logger.Errorln("An error occurred starting multicast:", err)
logger.Errorln("An error occurred starting multicast:", err)
}
}
@ -159,15 +169,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
}
@ -259,3 +274,28 @@ func (m *Yggdrasil) GetMTU() int {
func GetVersion() string {
return version.BuildVersion()
}
type ConfigSummary struct {
PublicKey string
IPv6Address string
IPv6Subnet string
}
func SummaryForConfig(b []byte) *ConfigSummary {
cfg := config.GenerateConfig()
if err := cfg.UnmarshalHJSON(b); err != nil {
return nil
}
pub := ed25519.PrivateKey(cfg.PrivateKey).Public().(ed25519.PublicKey)
hpub := hex.EncodeToString(pub)
addr := net.IP(address.AddrForKey(pub)[:])
snet := net.IPNet{
IP: append(address.SubnetForKey(pub)[:], 0, 0, 0, 0, 0, 0, 0, 0),
Mask: net.CIDRMask(64, 128),
}
return &ConfigSummary{
PublicKey: hpub,
IPv6Address: addr.String(),
IPv6Subnet: snet.String(),
}
}

View file

@ -1,5 +1,5 @@
//go:build ios
// +build ios
//go:build ios || darwin
// +build ios darwin
package mobile

View file

@ -1,5 +1,5 @@
//go:build !android && !ios
// +build !android,!ios
//go:build !android && !ios && !darwin
// +build !android,!ios,!darwin
package mobile

View file

@ -1,7 +1,7 @@
#!/bin/sh
# This script generates an MSI file for Yggdrasil for a given architecture. It
# needs to run on Windows within MSYS2 and Go 1.17 or later must be installed on
# needs to run on Windows within MSYS2 and Go 1.21 or later must be installed on
# the system and within the PATH. This is ran currently by GitHub Actions (see
# the workflows in the repository).
#
@ -16,20 +16,7 @@ then
fi
# Download the wix tools!
if [ ! -d wixbin ];
then
curl -LO https://wixtoolset.org/downloads/v3.14.0.3910/wix314-binaries.zip
if [ `md5sum wix314-binaries.zip | cut -f 1 -d " "` != "34f655cf108086838dd5a76d4318063b" ];
then
echo "wix package didn't match expected checksum"
exit 1
fi
mkdir -p wixbin
unzip -o wix314-binaries.zip -d wixbin || (
echo "failed to unzip WiX"
exit 1
)
fi
dotnet tool install --global wix --version 5.0.0
# Build Yggdrasil!
[ "${PKGARCH}" == "x64" ] && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./build
@ -61,6 +48,11 @@ PKGVERSIONMS=$(echo $PKGVERSION | tr - .)
if [ ! -d wintun ];
then
curl -o wintun.zip https://www.wintun.net/builds/wintun-0.14.1.zip
if [ `sha256sum wintun.zip | cut -f 1 -d " "` != "07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51" ];
then
echo "wintun package didn't match expected checksum"
exit 1
fi
unzip wintun.zip
fi
if [ $PKGARCH = "x64" ]; then
@ -101,7 +93,7 @@ cat > wix.xml << EOF
Description="Yggdrasil Network Installer"
Comments="Yggdrasil Network standalone router for Windows."
Manufacturer="github.com/yggdrasil-network"
InstallerVersion="200"
InstallerVersion="500"
InstallScope="perMachine"
Languages="1033"
Compressed="yes"
@ -205,5 +197,5 @@ EOF
# Generate the MSI
CANDLEFLAGS="-nologo"
LIGHTFLAGS="-nologo -spdb -sice:ICE71 -sice:ICE61"
wixbin/candle $CANDLEFLAGS -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj -arch ${PKGARCH} wix.xml && \
wixbin/light $LIGHTFLAGS -ext WixUtilExtension.dll -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.msi ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj
candle $CANDLEFLAGS -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj -arch ${PKGARCH} wix.xml && \
light $LIGHTFLAGS -ext WixUtilExtension.dll -out ${PKGNAME}-${PKGVERSION}-${PKGARCH}.msi ${PKGNAME}-${PKGVERSION}-${PKGARCH}.wixobj

View file

@ -6,7 +6,6 @@ CONFFILE="/etc/yggdrasil.conf"
pidfile="/run/${RC_SVCNAME}.pid"
command="/usr/bin/yggdrasil"
extra_started_commands="reload"
depend() {
use net dns logger
@ -42,12 +41,6 @@ start() {
eend $?
}
reload() {
ebegin "Reloading ${RC_SVCNAME}"
start-stop-daemon --signal HUP --pidfile "${pidfile}"
eend $?
}
stop() {
ebegin "Stopping ${RC_SVCNAME}"
start-stop-daemon --stop --pidfile "${pidfile}" --exec "${command}"

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

50
go.mod
View file

@ -1,47 +1,49 @@
module github.com/yggdrasil-network/yggdrasil-go
go 1.20
go 1.22
require (
github.com/Arceliar/ironwood v0.0.0-20230805085300-86206813435f
github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d
github.com/cheggaaa/pb/v3 v3.1.4
github.com/cheggaaa/pb/v3 v3.1.5
github.com/coder/websocket v1.8.12
github.com/gologme/log v1.3.0
github.com/hashicorp/go-syslog v1.0.0
github.com/hjson/hjson-go/v4 v4.3.0
github.com/hjson/hjson-go/v4 v4.4.0
github.com/kardianos/minwinsvc v1.0.2
github.com/quic-go/quic-go v0.39.0
github.com/vishvananda/netlink v1.1.0
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
github.com/quic-go/quic-go v0.48.2
github.com/vishvananda/netlink v1.3.0
github.com/wlynxg/anet v0.0.5
golang.org/x/crypto v0.33.0
golang.org/x/net v0.35.0
golang.org/x/sys v0.30.0
golang.org/x/text v0.22.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
golang.zx2c4.com/wireguard/windows v0.5.3
)
require (
github.com/bits-and-blooms/bitset v1.5.0 // indirect
github.com/bits-and-blooms/bloom/v3 v3.3.1 // indirect
github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/bits-and-blooms/bloom/v3 v3.7.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
github.com/rivo/uniseg v0.2.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
go.uber.org/mock v0.5.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/tools v0.23.0 // indirect
)
require (
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/olekukonko/tablewriter v0.0.5
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
github.com/vishvananda/netns v0.0.5 // indirect
suah.dev/protect v1.2.4
)

156
go.sum
View file

@ -1,137 +1,119 @@
github.com/Arceliar/ironwood v0.0.0-20230805085300-86206813435f h1:Fz0zG7ZyQQqk+ROnmHuGrIZO250Lx/YHmp9o48XE+Vw=
github.com/Arceliar/ironwood v0.0.0-20230805085300-86206813435f/go.mod h1:5x7fWW0mshe9WQ1lvSMmmHBYC3BeHH9gpwW5tz7cbfw=
github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3 h1:d8N0z+udAnbU5PdjpLSNPTWlqeU/nnYsQ42B6+879aw=
github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3/go.mod h1:SrrElc3FFMpYCODSr11jWbLFeOM8WsY+DbDY/l2AXF0=
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM=
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/bits-and-blooms/bitset v1.3.1/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8=
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.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo=
github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA=
github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bloom/v3 v3.7.0 h1:VfknkqV4xI+PsaDIsoHueyxVDZrfvMn56jeWUzvzdls=
github.com/bits-and-blooms/bloom/v3 v3.7.0/go.mod h1:VKlUSvp0lFIYqxJjzdnSsZEw4iHb1kOL2tfHTgyJBHg=
github.com/cheggaaa/pb/v3 v3.1.5 h1:QuuUzeM2WsAqG2gMqtzaWithDJv0i+i6UlnwSCI4QLk=
github.com/cheggaaa/pb/v3 v3.1.5/go.mod h1:CrxkeghYTXi1lQBEI7jSn+3svI3cuc19haAj6jM60XI=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/gologme/log v1.3.0 h1:l781G4dE+pbigClDSDzSaaYKtiueHCILUa/qSDsmHAo=
github.com/gologme/log v1.3.0/go.mod h1:yKT+DvIPdDdDoPtqFrFxheooyVmoqi0BAsw+erN3wA4=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hjson/hjson-go/v4 v4.3.0 h1:dyrzJdqqFGhHt+FSrs5n9s6b0fPM8oSJdWo+oS3YnJw=
github.com/hjson/hjson-go/v4 v4.3.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
github.com/hjson/hjson-go/v4 v4.4.0 h1:D/NPvqOCH6/eisTb5/ztuIS8GUvmpHaLOcNk1Bjr298=
github.com/hjson/hjson-go/v4 v4.4.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/kardianos/minwinsvc v1.0.2 h1:JmZKFJQrmTGa/WiW+vkJXKmfzdjabuEW4Tirj5lLdR0=
github.com/kardianos/minwinsvc v1.0.2/go.mod h1:LUZNYhNmxujx2tR7FbdxqYJ9XDDoCd3MQcl1o//FWl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/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.0 h1:AgP40iThFMY0bj8jGxROhw3S0FMGa8ryqsmi9tBH3So=
github.com/quic-go/quic-go v0.39.0/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
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/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
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/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/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/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=
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
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/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/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.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-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 v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
suah.dev/protect v1.2.4 h1:iVZG/zQB63FKNpITDYM/cXoAeCTIjCiXHuFVByJFDzg=
suah.dev/protect v1.2.4/go.mod h1:vVrquYO3u1Ep9Ez2z8x+6N6/czm+TBmWKZfiXU2tb54=

View file

@ -113,7 +113,7 @@ func SubnetForKey(publicKey ed25519.PublicKey) *Subnet {
return &snet
}
// GetKet returns the partial ed25519.PublicKey for the Address.
// GetKey returns the partial ed25519.PublicKey for the Address.
// This is used for key lookup.
func (a *Address) GetKey() ed25519.PublicKey {
var key [ed25519.PublicKeySize]byte
@ -141,7 +141,7 @@ func (a *Address) GetKey() ed25519.PublicKey {
return ed25519.PublicKey(key[:])
}
// GetKet returns the partial ed25519.PublicKey for the Subnet.
// GetKey returns the partial ed25519.PublicKey for the Subnet.
// This is used for key lookup.
func (s *Subnet) GetKey() ed25519.PublicKey {
var addr Address

View file

@ -12,7 +12,7 @@ type AddPeerRequest struct {
type AddPeerResponse struct{}
func (a *AdminSocket) addPeerHandler(req *AddPeerRequest, res *AddPeerResponse) error {
func (a *AdminSocket) addPeerHandler(req *AddPeerRequest, _ *AddPeerResponse) error {
u, err := url.Parse(req.Uri)
if err != nil {
return fmt.Errorf("unable to parse peering URI: %w", err)

View file

@ -83,6 +83,52 @@ func New(c *core.Core, log core.Logger, opts ...SetupOption) (*AdminSocket, erro
if a.config.listenaddr == "none" || a.config.listenaddr == "" {
return nil, nil
}
listenaddr := string(a.config.listenaddr)
u, err := url.Parse(listenaddr)
if err == nil {
switch strings.ToLower(u.Scheme) {
case "unix":
if _, err := os.Stat(u.Path); err == nil {
a.log.Debugln("Admin socket", u.Path, "already exists, trying to clean up")
if _, err := net.DialTimeout("unix", u.Path, time.Second*2); err == nil || err.(net.Error).Timeout() {
a.log.Errorln("Admin socket", u.Path, "already exists and is in use by another process")
os.Exit(1)
} else {
if err := os.Remove(u.Path); err == nil {
a.log.Debugln(u.Path, "was cleaned up")
} else {
a.log.Errorln(u.Path, "already exists and was not cleaned up:", err)
os.Exit(1)
}
}
}
a.listener, err = net.Listen("unix", u.Path)
if err == nil {
switch u.Path[:1] {
case "@": // maybe abstract namespace
default:
if err := os.Chmod(u.Path, 0660); err != nil {
a.log.Warnln("WARNING:", u.Path, "may have unsafe permissions!")
}
}
}
case "tcp":
a.listener, err = net.Listen("tcp", u.Host)
default:
a.listener, err = net.Listen("tcp", listenaddr)
}
} else {
a.listener, err = net.Listen("tcp", listenaddr)
}
if err != nil {
a.log.Errorf("Admin socket failed to listen: %v", err)
os.Exit(1)
}
a.log.Infof("%s admin socket listening on %s",
strings.ToUpper(a.listener.Addr().Network()),
a.listener.Addr().String())
_ = a.AddHandler("list", "List available commands", []string{}, func(_ json.RawMessage) (interface{}, error) {
res := &ListResponse{}
for name, handler := range a.handlers {
@ -201,10 +247,6 @@ func (a *AdminSocket) SetupAdminHandlers() {
return res, nil
},
)
//_ = a.AddHandler("getNodeInfo", []string{"key"}, t.proto.nodeinfo.nodeInfoAdminHandler)
//_ = a.AddHandler("debug_remoteGetSelf", []string{"key"}, t.proto.getSelfHandler)
//_ = a.AddHandler("debug_remoteGetPeers", []string{"key"}, t.proto.getPeersHandler)
//_ = a.AddHandler("debug_remoteGetDHT", []string{"key"}, t.proto.getDHTHandler)
}
// IsStarted returns true if the module has been started.
@ -237,51 +279,6 @@ func (a *AdminSocket) Stop() error {
// listen is run by start and manages API connections.
func (a *AdminSocket) listen() {
listenaddr := string(a.config.listenaddr)
u, err := url.Parse(listenaddr)
if err == nil {
switch strings.ToLower(u.Scheme) {
case "unix":
if _, err := os.Stat(listenaddr[7:]); err == nil {
a.log.Debugln("Admin socket", listenaddr[7:], "already exists, trying to clean up")
if _, err := net.DialTimeout("unix", listenaddr[7:], time.Second*2); err == nil || err.(net.Error).Timeout() {
a.log.Errorln("Admin socket", listenaddr[7:], "already exists and is in use by another process")
os.Exit(1)
} else {
if err := os.Remove(listenaddr[7:]); err == nil {
a.log.Debugln(listenaddr[7:], "was cleaned up")
} else {
a.log.Errorln(listenaddr[7:], "already exists and was not cleaned up:", err)
os.Exit(1)
}
}
}
a.listener, err = net.Listen("unix", listenaddr[7:])
if err == nil {
switch listenaddr[7:8] {
case "@": // maybe abstract namespace
default:
if err := os.Chmod(listenaddr[7:], 0660); err != nil {
a.log.Warnln("WARNING:", listenaddr[:7], "may have unsafe permissions!")
}
}
}
case "tcp":
a.listener, err = net.Listen("tcp", u.Host)
default:
// err = errors.New(fmt.Sprint("protocol not supported: ", u.Scheme))
a.listener, err = net.Listen("tcp", listenaddr)
}
} else {
a.listener, err = net.Listen("tcp", listenaddr)
}
if err != nil {
a.log.Errorf("Admin socket failed to listen: %v", err)
os.Exit(1)
}
a.log.Infof("%s admin socket listening on %s",
strings.ToUpper(a.listener.Addr().Network()),
a.listener.Addr().String())
defer a.listener.Close()
for {
conn, err := a.listener.Accept()
@ -309,23 +306,6 @@ func (a *AdminSocket) handleRequest(conn net.Conn) {
defer conn.Close()
/*
defer func() {
r := recover()
if r != nil {
fmt.Println("ERROR:", r)
a.log.Debugln("Admin socket error:", r)
if err := encoder.Encode(&ErrorResponse{
Error: "Check your syntax and input types",
}); err != nil {
fmt.Println("ERROR 2:", err)
a.log.Debugln("Admin socket JSON encode error:", err)
}
conn.Close()
}
}()
*/
for {
var err error
var buf json.RawMessage
@ -376,13 +356,15 @@ type DataUnit uint64
func (d DataUnit) String() string {
switch {
case d > 1024*1024*1024*1024:
return fmt.Sprintf("%2.ftb", float64(d)/1024/1024/1024/1024)
case d > 1024*1024*1024:
return fmt.Sprintf("%2.fgb", float64(d)/1024/1024/1024)
case d > 1024*1024:
return fmt.Sprintf("%2.fmb", float64(d)/1024/1024)
case d >= 1024*1024*1024*1024:
return fmt.Sprintf("%2.1fTB", float64(d)/1024/1024/1024/1024)
case d >= 1024*1024*1024:
return fmt.Sprintf("%2.1fGB", float64(d)/1024/1024/1024)
case d >= 1024*1024:
return fmt.Sprintf("%2.1fMB", float64(d)/1024/1024)
case d >= 100:
return fmt.Sprintf("%2.1fKB", float64(d)/1024)
default:
return fmt.Sprintf("%2.fkb", float64(d)/1024)
return fmt.Sprintf("%dB", d)
}
}

View file

@ -3,7 +3,7 @@ package admin
import (
"encoding/hex"
"net"
"sort"
"slices"
"strings"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
@ -23,7 +23,7 @@ type PathEntry struct {
Sequence uint64 `json:"sequence"`
}
func (a *AdminSocket) getPathsHandler(req *GetPathsRequest, res *GetPathsResponse) error {
func (a *AdminSocket) getPathsHandler(_ *GetPathsRequest, res *GetPathsResponse) error {
paths := a.core.GetPaths()
res.Paths = make([]PathEntry, 0, len(paths))
for _, p := range paths {
@ -35,8 +35,8 @@ func (a *AdminSocket) getPathsHandler(req *GetPathsRequest, res *GetPathsRespons
Sequence: p.Sequence,
})
}
sort.SliceStable(res.Paths, func(i, j int) bool {
return strings.Compare(res.Paths[i].PublicKey, res.Paths[j].PublicKey) < 0
slices.SortStableFunc(res.Paths, func(a, b PathEntry) int {
return strings.Compare(a.PublicKey, b.PublicKey)
})
return nil
}

View file

@ -3,7 +3,8 @@ package admin
import (
"encoding/hex"
"net"
"sort"
"slices"
"strings"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
@ -24,14 +25,18 @@ type PeerEntry struct {
PublicKey string `json:"key"`
Port uint64 `json:"port"`
Priority uint64 `json:"priority"`
Cost uint64 `json:"cost"`
RXBytes DataUnit `json:"bytes_recvd,omitempty"`
TXBytes DataUnit `json:"bytes_sent,omitempty"`
RXRate DataUnit `json:"rate_recvd,omitempty"`
TXRate DataUnit `json:"rate_sent,omitempty"`
Uptime float64 `json:"uptime,omitempty"`
LastError string `json:"last_error,omitempty"`
Latency time.Duration `json:"latency,omitempty"`
LastErrorTime time.Duration `json:"last_error_time,omitempty"`
LastError string `json:"last_error,omitempty"`
}
func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersResponse) error {
func (a *AdminSocket) getPeersHandler(_ *GetPeersRequest, res *GetPeersResponse) error {
peers := a.core.GetPeers()
res.Peers = make([]PeerEntry, 0, len(peers))
for _, p := range peers {
@ -40,11 +45,17 @@ func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersRespons
Up: p.Up,
Inbound: p.Inbound,
Priority: uint64(p.Priority), // can't be uint8 thanks to gobind
Cost: p.Cost,
URI: p.URI,
RXBytes: DataUnit(p.RXBytes),
TXBytes: DataUnit(p.TXBytes),
RXRate: DataUnit(p.RXRate),
TXRate: DataUnit(p.TXRate),
Uptime: p.Uptime.Seconds(),
}
if p.Latency > 0 {
peer.Latency = p.Latency
}
if addr := address.AddrForKey(p.Key); addr != nil {
peer.PublicKey = hex.EncodeToString(p.Key)
peer.IPAddress = net.IP(addr[:]).String()
@ -55,11 +66,26 @@ func (a *AdminSocket) getPeersHandler(req *GetPeersRequest, res *GetPeersRespons
}
res.Peers = append(res.Peers, peer)
}
sort.Slice(res.Peers, func(i, j int) bool {
if res.Peers[i].Port == res.Peers[j].Port {
return res.Peers[i].Priority < res.Peers[j].Priority
slices.SortStableFunc(res.Peers, func(a, b PeerEntry) int {
if !a.Inbound && b.Inbound {
return -1
}
return res.Peers[i].Port < res.Peers[j].Port
if a.Inbound && !b.Inbound {
return 1
}
if d := strings.Compare(a.PublicKey, b.PublicKey); d != 0 {
return d
}
if d := a.Priority - b.Priority; d != 0 {
return int(d)
}
if d := a.Cost - b.Cost; d != 0 {
return int(d)
}
if d := a.Uptime - b.Uptime; d != 0 {
return int(d)
}
return 0
})
return nil
}

View file

@ -17,7 +17,7 @@ type GetSelfResponse struct {
Subnet string `json:"subnet"`
}
func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse) error {
func (a *AdminSocket) getSelfHandler(_ *GetSelfRequest, res *GetSelfResponse) error {
self := a.core.GetSelf()
snet := a.core.Subnet()
res.BuildName = version.BuildName()

View file

@ -3,7 +3,7 @@ package admin
import (
"encoding/hex"
"net"
"sort"
"slices"
"strings"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
@ -23,7 +23,7 @@ type SessionEntry struct {
Uptime float64 `json:"uptime"`
}
func (a *AdminSocket) getSessionsHandler(req *GetSessionsRequest, res *GetSessionsResponse) error {
func (a *AdminSocket) getSessionsHandler(_ *GetSessionsRequest, res *GetSessionsResponse) error {
sessions := a.core.GetSessions()
res.Sessions = make([]SessionEntry, 0, len(sessions))
for _, s := range sessions {
@ -36,8 +36,8 @@ func (a *AdminSocket) getSessionsHandler(req *GetSessionsRequest, res *GetSessio
Uptime: s.Uptime.Seconds(),
})
}
sort.SliceStable(res.Sessions, func(i, j int) bool {
return strings.Compare(res.Sessions[i].PublicKey, res.Sessions[j].PublicKey) < 0
slices.SortStableFunc(res.Sessions, func(a, b SessionEntry) int {
return strings.Compare(a.PublicKey, b.PublicKey)
})
return nil
}

View file

@ -3,7 +3,7 @@ package admin
import (
"encoding/hex"
"net"
"sort"
"slices"
"strings"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
@ -20,11 +20,9 @@ type TreeEntry struct {
PublicKey string `json:"key"`
Parent string `json:"parent"`
Sequence uint64 `json:"sequence"`
//Port uint64 `json:"port"`
//Rest uint64 `json:"rest"`
}
func (a *AdminSocket) getTreeHandler(req *GetTreeRequest, res *GetTreeResponse) error {
func (a *AdminSocket) getTreeHandler(_ *GetTreeRequest, res *GetTreeResponse) error {
tree := a.core.GetTree()
res.Tree = make([]TreeEntry, 0, len(tree))
for _, d := range tree {
@ -34,12 +32,10 @@ func (a *AdminSocket) getTreeHandler(req *GetTreeRequest, res *GetTreeResponse)
PublicKey: hex.EncodeToString(d.Key[:]),
Parent: hex.EncodeToString(d.Parent[:]),
Sequence: d.Sequence,
//Port: d.Port,
//Rest: d.Rest,
})
}
sort.SliceStable(res.Tree, func(i, j int) bool {
return strings.Compare(res.Tree[i].PublicKey, res.Tree[j].PublicKey) < 0
slices.SortStableFunc(res.Tree, func(a, b TreeEntry) int {
return strings.Compare(a.PublicKey, b.PublicKey)
})
return nil
}

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

@ -12,7 +12,7 @@ type RemovePeerRequest struct {
type RemovePeerResponse struct{}
func (a *AdminSocket) removePeerHandler(req *RemovePeerRequest, res *RemovePeerResponse) error {
func (a *AdminSocket) removePeerHandler(req *RemovePeerRequest, _ *RemovePeerResponse) error {
u, err := url.Parse(req.Uri)
if err != nil {
return fmt.Errorf("unable to parse peering URI: %w", err)

View file

@ -40,27 +40,28 @@ 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"`
PrivateKey KeyBytes `json:",omitempty" comment:"Your private key. DO NOT share this with anyone!"`
PrivateKeyPath string `json:",omitempty" comment:"The path to your private key file in PEM format."`
Certificate *tls.Certificate `json:"-"`
Peers []string `comment:"List of 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."`
Peers []string `comment:"List of outbound peer connection strings (e.g. tls://a.b.c.d:e or\nsocks://a.b.c.d:e/f.g.h.i:j). Connection strings can contain options,\nsee https://yggdrasil-network.github.io/configurationref.html#peers.\nYggdrasil has no concept of bootstrap nodes - all network traffic\nwill transit peer connections. Therefore make sure to only peer with\nnearby nodes that have good connectivity and low latency. Avoid adding\npeers to this list from distant countries as this will worsen your\nnode's connectivity and performance considerably."`
InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ \"tls://a.b.c.d:e\" ] }.\nYou should only use this option if your machine is multi-homed and you\nwant to establish outbound peer connections on different interfaces.\nOtherwise you should use \"Peers\"."`
Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nThis is not required if you wish to establish outbound peerings only.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."`
AdminListen string `json:",omitempty" comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Regex is a regular expression which is matched against an\ninterface name, and interfaces use the first configuration that they\nmatch against. Beacon controls whether or not your node advertises its\npresence to others, whereas Listen controls whether or not your node\nlistens out for and tries to connect to other advertising nodes. See\nhttps://yggdrasil-network.github.io/configurationref.html#multicastinterfaces\nfor more supported options."`
AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast.\nWARNING: THIS IS NOT A FIREWALL and DOES NOT limit who can reach\nopen ports or services running on your machine!"`
IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."`
IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
LogLookups bool `json:",omitempty"`
NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."`
NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."`
NodeInfo map[string]interface{} `comment:"Optional nodeinfo. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."`
}
type MulticastInterfaceConfig struct {
Regex string
Beacon bool
Listen bool
Port uint16
Priority uint64 // really uint8, but gobind won't export it
Port uint16 `json:",omitempty"`
Priority uint64 `json:",omitempty"` // really uint8, but gobind won't export it
Password string
}

View file

@ -17,6 +17,7 @@ func getDefaults() platformDefaultParameters {
DefaultMulticastInterfaces: []MulticastInterfaceConfig{
{Regex: "en.*", Beacon: true, Listen: true},
{Regex: "bridge.*", Beacon: true, Listen: true},
{Regex: "awdl0", Beacon: false, Listen: false},
},
// TUN

View file

@ -30,9 +30,13 @@ type PeerInfo struct {
Coords []uint64
Port uint64
Priority uint8
Cost uint64
RXBytes uint64
TXBytes uint64
RXRate uint64
TXRate uint64
Uptime time.Duration
Latency time.Duration
}
type TreeEntryInfo struct {
@ -85,6 +89,8 @@ func (c *Core) GetPeers() []PeerInfo {
peerinfo.Inbound = state.linkType == linkTypeIncoming
peerinfo.RXBytes = atomic.LoadUint64(&c.rx)
peerinfo.TXBytes = atomic.LoadUint64(&c.tx)
peerinfo.RXRate = atomic.LoadUint64(&c.rxrate)
peerinfo.TXRate = atomic.LoadUint64(&c.txrate)
peerinfo.Uptime = time.Since(c.up)
}
if p, ok := conns[conn]; ok {
@ -92,6 +98,8 @@ func (c *Core) GetPeers() []PeerInfo {
peerinfo.Root = p.Root
peerinfo.Port = p.Port
peerinfo.Priority = p.Priority
peerinfo.Latency = p.Latency
peerinfo.Cost = p.Cost
}
peers = append(peers, peerinfo)
}
@ -146,7 +154,14 @@ func (c *Core) GetSessions() []SessionInfo {
// parsed from a string of the form e.g. "tcp://a.b.c.d:e". In the case of a
// link-local address, the interface should be provided as the second argument.
func (c *Core) Listen(u *url.URL, sintf string) (*Listener, error) {
return c.links.listen(u, sintf)
return c.links.listen(u, sintf, false)
}
// ListenLocal starts a listener, like the Listen function, but is used for
// more trustworthy situations where you want to ignore AllowedPublicKeys, i.e.
// with multicast listeners.
func (c *Core) ListenLocal(u *url.URL, sintf string) (*Listener, error) {
return c.links.listen(u, sintf, true)
}
// Address gets the IPv6 address of the Yggdrasil node. This is always a /128

View file

@ -40,6 +40,7 @@ type Core struct {
tls *tls.Config // immutable after startup
//_peers map[Peer]*linkInfo // configurable after startup
_listeners map[ListenAddress]struct{} // configurable after startup
peerFilter func(ip net.IP) bool // immutable after startup
nodeinfo NodeInfo // immutable after startup
nodeinfoPrivacy NodeInfoPrivacy // immutable after startup
_allowedPublicKeys map[[32]byte]struct{} // configurable after startup
@ -127,7 +128,7 @@ func New(cert *tls.Certificate, logger Logger, opts ...SetupOption) (*Core, erro
c.log.Errorf("Invalid listener URI %q specified, ignoring\n", listenaddr)
continue
}
if _, err = c.links.listen(u, ""); err != nil {
if _, err = c.links.listen(u, "", false); err != nil {
c.log.Errorf("Failed to start listener %q: %s\n", listenaddr, err)
}
}
@ -212,7 +213,7 @@ func (c *Core) ReadFrom(p []byte) (n int, from net.Addr, err error) {
func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) {
buf := allocBytes(0)
defer freeBytes(buf)
defer func() { freeBytes(buf) }()
buf = append(buf, typeSessionTraffic)
buf = append(buf, p...)
n, err = c.PacketConn.WriteTo(buf, addr)

View file

@ -25,6 +25,27 @@ func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger {
return l
}
func require_NoError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatal(err)
}
}
func require_Equal[T comparable](t *testing.T, a, b T) {
t.Helper()
if a != b {
t.Fatalf("%v != %v", a, b)
}
}
func require_True(t *testing.T, a bool) {
t.Helper()
if !a {
t.Fatal("expected true")
}
}
// CreateAndConnectTwo creates two nodes. nodeB connects to nodeA.
// Verbosity flag is passed to logger.
func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) {
@ -201,3 +222,69 @@ func BenchmarkCore_Start_Transfer(b *testing.B) {
}
<-done
}
func TestAllowedPublicKeys(t *testing.T) {
logger := GetLoggerWithPrefix("", false)
cfgA, cfgB := config.GenerateConfig(), config.GenerateConfig()
require_NoError(t, cfgA.GenerateSelfSignedCertificate())
require_NoError(t, cfgB.GenerateSelfSignedCertificate())
nodeA, err := New(cfgA.Certificate, logger, AllowedPublicKey("abcdef"))
require_NoError(t, err)
defer nodeA.Stop()
nodeB, err := New(cfgB.Certificate, logger)
require_NoError(t, err)
defer nodeB.Stop()
u, err := url.Parse("tcp://localhost:0")
require_NoError(t, err)
l, err := nodeA.Listen(u, "")
require_NoError(t, err)
u, err = url.Parse("tcp://" + l.Addr().String())
require_NoError(t, err)
require_NoError(t, nodeB.AddPeer(u, ""))
time.Sleep(time.Second)
peers := nodeB.GetPeers()
require_Equal(t, len(peers), 1)
require_True(t, !peers[0].Up)
require_True(t, peers[0].LastError != nil)
}
func TestAllowedPublicKeysLocal(t *testing.T) {
logger := GetLoggerWithPrefix("", false)
cfgA, cfgB := config.GenerateConfig(), config.GenerateConfig()
require_NoError(t, cfgA.GenerateSelfSignedCertificate())
require_NoError(t, cfgB.GenerateSelfSignedCertificate())
nodeA, err := New(cfgA.Certificate, logger, AllowedPublicKey("abcdef"))
require_NoError(t, err)
defer nodeA.Stop()
nodeB, err := New(cfgB.Certificate, logger)
require_NoError(t, err)
defer nodeB.Stop()
u, err := url.Parse("tcp://localhost:0")
require_NoError(t, err)
l, err := nodeA.ListenLocal(u, "")
require_NoError(t, err)
u, err = url.Parse("tcp://" + l.Addr().String())
require_NoError(t, err)
require_NoError(t, nodeB.AddPeer(u, ""))
time.Sleep(time.Second)
peers := nodeB.GetPeers()
require_Equal(t, len(peers), 1)
require_True(t, peers[0].Up)
require_True(t, peers[0].LastError == nil)
}

View file

@ -12,6 +12,8 @@ func init() {
envVarName := "PPROFLISTEN"
if hostPort := os.Getenv(envVarName); hostPort != "" {
fmt.Fprintf(os.Stderr, "DEBUG: Starting pprof on %s\n", hostPort)
go fmt.Println(http.ListenAndServe(hostPort, nil))
go func() {
fmt.Fprintf(os.Stderr, "DEBUG: %s", http.ListenAndServe(hostPort, nil))
}()
}
}

View file

@ -4,11 +4,10 @@ import (
"bytes"
"context"
"encoding/hex"
"errors"
"fmt"
"io"
"math"
"net"
"net/netip"
"net/url"
"strconv"
"strings"
@ -28,6 +27,9 @@ const (
linkTypeIncoming // Incoming connection
)
const defaultBackoffLimit = time.Second << 12 // 1h8m16s
const minimumBackoffLimit = time.Second * 30
type links struct {
phony.Inbox
core *Core
@ -36,8 +38,11 @@ type links struct {
unix *linkUNIX // UNIX interface support
socks *linkSOCKS // SOCKS interface support
quic *linkQUIC // QUIC interface support
ws *linkWS // WS interface support
wss *linkWSS // WSS interface support
// _links can only be modified safely from within the links actor
_links map[linkInfo]*link // *link is nil if connection in progress
_links map[linkInfo]*link // *link is nil if connection in progress
_listeners map[*Listener]context.CancelFunc
}
type linkProtocol interface {
@ -61,7 +66,7 @@ type link struct {
// The remaining fields can only be modified safely from within the links actor
_conn *linkConn // Connected link, if any, nil if not connected
_err error // Last error on the connection, if any
_errtime time.Time // Last time an error occured
_errtime time.Time // Last time an error occurred
}
type linkOptions struct {
@ -69,6 +74,7 @@ type linkOptions struct {
priority uint8
tlsSNI string
password []byte
maxBackoff time.Duration
}
type Listener struct {
@ -81,13 +87,6 @@ func (l *Listener) Addr() net.Addr {
return l.listener.Addr()
}
func (l *Listener) Close() error {
l.Cancel()
err := l.listener.Close()
<-l.ctx.Done()
return err
}
func (l *links) init(c *Core) error {
l.core = c
l.tcp = l.newLinkTCP()
@ -95,33 +94,50 @@ func (l *links) init(c *Core) error {
l.unix = l.newLinkUNIX()
l.socks = l.newLinkSOCKS()
l.quic = l.newLinkQUIC()
l.ws = l.newLinkWS()
l.wss = l.newLinkWSS()
l._links = make(map[linkInfo]*link)
l._listeners = make(map[*Listener]context.CancelFunc)
var listeners []ListenAddress
phony.Block(c, func() {
listeners = make([]ListenAddress, 0, len(c.config._listeners))
for listener := range c.config._listeners {
listeners = append(listeners, listener)
}
})
l.Act(nil, l._updateAverages)
return nil
}
func (l *links) _updateAverages() {
select {
case <-l.core.ctx.Done():
return
default:
}
for _, l := range l._links {
if l._conn == nil {
continue
}
rx := atomic.LoadUint64(&l._conn.rx)
tx := atomic.LoadUint64(&l._conn.tx)
lastrx := atomic.LoadUint64(&l._conn.lastrx)
lasttx := atomic.LoadUint64(&l._conn.lasttx)
atomic.StoreUint64(&l._conn.rxrate, rx-lastrx)
atomic.StoreUint64(&l._conn.txrate, tx-lasttx)
atomic.StoreUint64(&l._conn.lastrx, rx)
atomic.StoreUint64(&l._conn.lasttx, tx)
}
time.AfterFunc(time.Second, func() {
l.Act(nil, l._updateAverages)
})
}
func (l *links) shutdown() {
phony.Block(l.tcp, func() {
for l := range l.tcp._listeners {
_ = l.Close()
phony.Block(l, func() {
for _, cancel := range l._listeners {
cancel()
}
})
phony.Block(l.tls, func() {
for l := range l.tls._listeners {
_ = l.Close()
}
})
phony.Block(l.unix, func() {
for l := range l.unix._listeners {
_ = l.Close()
for _, link := range l._links {
if link._conn != nil {
_ = link._conn.Close()
}
}
})
}
@ -134,8 +150,11 @@ 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 ErrLinkPasswordInvalid = linkError("invalid password supplied")
const ErrLinkUnrecognisedSchema = linkError("link schema unknown")
const ErrLinkMaxBackoffInvalid = linkError("max backoff duration invalid")
const ErrLinkSNINotSupported = linkError("SNI not supported on this link type")
const ErrLinkNoSuitableIPs = linkError("peer has no suitable addresses")
func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
var retErr error
@ -150,7 +169,9 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
// Collect together the link options, these are global options
// that are not specific to any given protocol.
var options linkOptions
options := linkOptions{
maxBackoff: defaultBackoffLimit,
}
for _, pubkey := range u.Query()["key"] {
sigPub, err := hex.DecodeString(pubkey)
if err != nil {
@ -179,6 +200,30 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
}
options.password = []byte(p)
}
if p := u.Query().Get("maxbackoff"); p != "" {
d, err := time.ParseDuration(p)
if err != nil || d < minimumBackoffLimit {
retErr = ErrLinkMaxBackoffInvalid
return
}
options.maxBackoff = d
}
// SNI headers must contain hostnames and not IP addresses, so we must make sure
// that we do not populate the SNI with an IP literal. We do this by splitting
// the host-port combo from the query option and then seeing if it parses to an
// IP address successfully or not.
if sni := u.Query().Get("sni"); sni != "" {
if net.ParseIP(sni) == nil {
options.tlsSNI = sni
}
}
// If the SNI is not configured still because the above failed then we'll try
// again but this time we'll use the host part of the peering URI instead.
if options.tlsSNI == "" {
if host, _, err := net.SplitHostPort(u.Host); err == nil && net.ParseIP(host) == nil {
options.tlsSNI = host
}
}
// If we think we're already connected to this peer, load up
// the existing peer state. Try to kick the peer if possible,
@ -219,8 +264,13 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
// The caller should check the return value to decide whether
// or not to give up trying.
backoffNow := func() bool {
backoff++
duration := time.Second * time.Duration(math.Exp2(float64(backoff)))
if backoff < 32 {
backoff++
}
duration := time.Second << backoff
if duration > options.maxBackoff {
duration = options.maxBackoff
}
select {
case <-state.kick:
return true
@ -233,6 +283,12 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
}
}
// resetBackoff is called by the connection handler when the
// handshake has successfully completed.
resetBackoff := func() {
backoff = 0
}
// The goroutine is responsible for attempting the connection
// and then running the handler. If the connection is persistent
// then the loop will run endlessly, using backoffs as needed.
@ -257,7 +313,10 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
}
conn, err := l.connect(state.ctx, u, info, options)
if err != nil {
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
@ -306,10 +365,12 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
// Give the connection to the handler. The handler will block
// for the lifetime of the connection.
if err = l.handler(linkType, options, lc); err != nil && err != io.EOF {
l.core.log.Debugf("Link %s error: %s\n", info.uri, err)
} else {
backoff = 0
switch err = l.handler(linkType, options, lc, resetBackoff, false); {
case err == nil:
case errors.Is(err, io.EOF):
case errors.Is(err, net.ErrClosed):
default:
l.core.log.Debugf("Link %s error: %s\n", u.Host, err)
}
// The handler has stopped running so the connection is dead,
@ -318,9 +379,11 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
_ = lc.Close()
phony.Block(l, func() {
state._conn = nil
if state._err = err; state._err != nil {
state._errtime = time.Now()
if err == nil {
err = fmt.Errorf("remote side closed the connection")
}
state._err = err
state._errtime = time.Now()
})
// If the link is persistently configured, back off if needed
@ -329,15 +392,16 @@ func (l *links) add(u *url.URL, sintf string, linkType linkType) error {
if backoffNow() {
continue
}
return
}
// Ephemeral or incoming connections don't reconnect.
return
}
}()
})
return retErr
}
func (l *links) remove(u *url.URL, sintf string, linkType linkType) error {
func (l *links) remove(u *url.URL, sintf string, _ linkType) error {
var retErr error
phony.Block(l, func() {
// Generate the link info and see whether we think we already
@ -364,8 +428,8 @@ func (l *links) remove(u *url.URL, sintf string, linkType linkType) error {
return retErr
}
func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
ctx, cancel := context.WithCancel(l.core.ctx)
func (l *links) listen(u *url.URL, sintf string, local bool) (*Listener, error) {
ctx, ctxcancel := context.WithCancel(l.core.ctx)
var protocol linkProtocol
switch strings.ToLower(u.Scheme) {
case "tcp":
@ -376,15 +440,26 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
protocol = l.unix
case "quic":
protocol = l.quic
case "ws":
protocol = l.ws
case "wss":
protocol = l.wss
default:
cancel()
ctxcancel()
return nil, ErrLinkUnrecognisedSchema
}
listener, err := protocol.listen(ctx, u, sintf)
if err != nil {
cancel()
ctxcancel()
return nil, err
}
addr := listener.Addr()
cancel := func() {
ctxcancel()
if err := listener.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
l.core.log.Warnf("Error closing %s listener %s: %s", strings.ToUpper(u.Scheme), addr, err)
}
}
li := &Listener{
listener: listener,
ctx: ctx,
@ -406,11 +481,19 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
options.password = []byte(p)
}
phony.Block(l, func() {
l._listeners[li] = cancel
})
go func() {
l.core.log.Printf("%s listener started on %s", strings.ToUpper(u.Scheme), listener.Addr())
defer l.core.log.Printf("%s listener stopped on %s", strings.ToUpper(u.Scheme), listener.Addr())
l.core.log.Infof("%s listener started on %s", strings.ToUpper(u.Scheme), addr)
defer phony.Block(l, func() {
cancel()
delete(l._listeners, li)
l.core.log.Infof("%s listener stopped on %s", strings.ToUpper(u.Scheme), addr)
})
for {
conn, err := listener.Accept()
conn, err := li.listener.Accept()
if err != nil {
return
}
@ -466,13 +549,22 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
// Store the state of the link so that it can be queried later.
l._links[info] = state
})
defer phony.Block(l, func() {
if l._links[info] == state {
delete(l._links, info)
}
})
if lc == nil {
return
}
// Give the connection to the handler. The handler will block
// for the lifetime of the connection.
if err = l.handler(linkTypeIncoming, options, lc); err != nil && err != io.EOF {
switch err = l.handler(linkTypeIncoming, options, lc, nil, local); {
case err == nil:
case errors.Is(err, io.EOF):
case errors.Is(err, net.ErrClosed):
default:
l.core.log.Debugf("Link %s error: %s\n", u.Host, err)
}
@ -480,11 +572,6 @@ func (l *links) listen(u *url.URL, sintf string) (*Listener, error) {
// try to close the underlying socket just in case and then
// drop the link state.
_ = lc.Close()
phony.Block(l, func() {
if l._links[info] == state {
delete(l._links, info)
}
})
}(conn)
}
}()
@ -497,36 +584,24 @@ func (l *links) connect(ctx context.Context, u *url.URL, info linkInfo, options
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
case "ws":
dialer = l.ws
case "wss":
dialer = l.wss
default:
return nil, ErrLinkUnrecognisedSchema
}
return dialer.dial(ctx, u, info, options)
}
func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn) error {
func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn, success func(), local bool) error {
meta := version_getBaseMetadata()
meta.publicKey = l.core.public
meta.priority = options.priority
@ -541,13 +616,14 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn) e
switch {
case err != nil:
return fmt.Errorf("write handshake: %w", err)
case err == nil && n != len(metaBytes):
case n != len(metaBytes):
return fmt.Errorf("incomplete handshake send")
}
meta = version_metadata{}
base := version_getBaseMetadata()
if !meta.decode(conn, options.password) {
return conn.Close()
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)",
@ -568,19 +644,21 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn) e
}
}
// Check if we're authorized to connect to this key / IP
var allowed map[[32]byte]struct{}
phony.Block(l.core, func() {
allowed = l.core.config._allowedPublicKeys
})
isallowed := len(allowed) == 0
for k := range allowed {
if bytes.Equal(k[:], meta.publicKey) {
isallowed = true
break
if !local {
var allowed map[[32]byte]struct{}
phony.Block(l.core, func() {
allowed = l.core.config._allowedPublicKeys
})
isallowed := len(allowed) == 0
for k := range allowed {
if bytes.Equal(k[:], meta.publicKey) {
isallowed = true
break
}
}
if linkType == linkTypeIncoming && !isallowed {
return fmt.Errorf("node public key %q is not in AllowedPublicKeys", hex.EncodeToString(meta.publicKey))
}
}
if linkType == linkTypeIncoming && !isallowed {
return fmt.Errorf("node public key %q is not in AllowedPublicKeys", hex.EncodeToString(meta.publicKey))
}
dir := "outbound"
@ -596,6 +674,9 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn) e
}
l.core.log.Infof("Connected %s: %s, source %s",
dir, remoteStr, localStr)
if success != nil {
success()
}
err = l.core.HandleConn(meta.publicKey, conn, priority)
switch err {
@ -606,30 +687,70 @@ func (l *links) handler(linkType linkType, options linkOptions, conn net.Conn) e
l.core.log.Infof("Disconnected %s: %s, source %s; error: %s",
dir, remoteStr, localStr, err)
}
return nil
return err
}
func (l *links) findSuitableIP(url *url.URL, fn func(hostname string, ip net.IP, port int) (net.Conn, error)) (net.Conn, error) {
host, p, err := net.SplitHostPort(url.Host)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(p)
if err != nil {
return nil, err
}
resp, err := net.LookupIP(host)
if err != nil {
return nil, err
}
var _ips [64]net.IP
ips := _ips[:0]
for _, ip := range resp {
switch {
case ip.IsUnspecified():
continue
case ip.IsMulticast():
continue
case ip.IsLinkLocalMulticast():
continue
case ip.IsInterfaceLocalMulticast():
continue
case l.core.config.peerFilter != nil && !l.core.config.peerFilter(ip):
continue
}
ips = append(ips, ip)
}
if len(ips) == 0 {
return nil, ErrLinkNoSuitableIPs
}
for _, ip := range ips {
var conn net.Conn
if conn, err = fn(host, ip, port); err != nil {
url := *url
url.RawQuery = ""
l.core.log.Debugln("Dialling", url.Redacted(), "reported error:", err)
continue
}
return conn, nil
}
return nil, err
}
func urlForLinkInfo(u url.URL) url.URL {
u.RawQuery = ""
if host, _, err := net.SplitHostPort(u.Host); err == nil {
if addr, err := netip.ParseAddr(host); err == nil {
// For peers that look like multicast peers (i.e.
// link-local addresses), we will ignore the port number,
// otherwise we might open multiple connections to them.
if addr.IsLinkLocalUnicast() {
u.Host = fmt.Sprintf("[%s]", addr.String())
}
}
}
return u
}
type linkConn struct {
// tx and rx are at the beginning of the struct to ensure 64-bit alignment
// on 32-bit platforms, see https://pkg.go.dev/sync/atomic#pkg-note-BUG
rx uint64
tx uint64
up time.Time
rx uint64
tx uint64
rxrate uint64
txrate uint64
lastrx uint64
lasttx uint64
up time.Time
net.Conn
}

View file

@ -3,6 +3,7 @@ package core
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/url"
"time"
@ -24,7 +25,7 @@ type linkQUICStream struct {
}
type linkQUICListener struct {
*quic.EarlyListener
*quic.Listener
ch <-chan *linkQUICStream
}
@ -50,45 +51,56 @@ func (l *links) newLinkQUIC() *linkQUIC {
}
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
tlsconfig := l.tlsconfig.Clone()
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
tlsconfig.ServerName = hostname
tlsconfig.MinVersion = tls.VersionTLS12
tlsconfig.MaxVersion = tls.VersionTLS13
hostport := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
qc, err := quic.DialAddr(ctx, hostport, l.tlsconfig, l.quicconfig)
if err != nil {
return nil, err
}
qs, err := qc.OpenStreamSync(ctx)
if err != nil {
return nil, err
}
return &linkQUICStream{
Connection: qc,
Stream: qs,
}, nil
})
}
func (l *linkQUIC) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
ql, err := quic.ListenAddrEarly(url.Host, l.tlsconfig, l.quicconfig)
ql, err := quic.ListenAddr(url.Host, l.tlsconfig, l.quicconfig)
if err != nil {
return nil, err
}
ch := make(chan *linkQUICStream)
lql := &linkQUICListener{
EarlyListener: ql,
ch: ch,
Listener: ql,
ch: ch,
}
go func() {
for {
qc, err := ql.Accept(ctx)
if err != nil {
switch err {
case context.Canceled, context.DeadlineExceeded:
ql.Close()
fallthrough
case quic.ErrServerClosed:
return
}
qs, err := qc.AcceptStream(ctx)
if err != nil {
ql.Close()
return
}
ch <- &linkQUICStream{
Connection: qc,
Stream: qs,
case nil:
qs, err := qc.AcceptStream(ctx)
if err != nil {
_ = qc.CloseWithError(1, fmt.Sprintf("stream error: %s", err))
continue
}
ch <- &linkQUICStream{
Connection: qc,
Stream: qs,
}
}
}
}()

View file

@ -2,6 +2,7 @@ package core
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/url"
@ -29,12 +30,36 @@ func (l *linkSOCKS) dial(_ context.Context, url *url.URL, info linkInfo, options
}
proxyAuth.Password, _ = url.User.Password()
}
dialer, err := proxy.SOCKS5("tcp", url.Host, proxyAuth, proxy.Direct)
if err != nil {
return nil, fmt.Errorf("failed to configure proxy")
}
pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/")
return dialer.Dial("tcp", pathtokens[0])
tlsconfig := l.tls.config.Clone()
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
hostport := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
dialer, err := l.tcp.dialerFor(&net.TCPAddr{
IP: ip,
Port: port,
}, info.sintf)
if err != nil {
return nil, err
}
proxy, err := proxy.SOCKS5("tcp", hostport, proxyAuth, dialer)
if err != nil {
return nil, err
}
pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/")
conn, err := proxy.Dial("tcp", pathtokens[0])
if err != nil {
return nil, err
}
if url.Scheme == "sockstls" {
tlsconfig.ServerName = hostname
tlsconfig.MinVersion = tls.VersionTLS12
tlsconfig.MaxVersion = tls.VersionTLS13
if sni := options.tlsSNI; sni != "" {
tlsconfig.ServerName = sni
}
conn = tls.Client(conn, tlsconfig)
}
return conn, nil
})
}
func (l *linkSOCKS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {

View file

@ -5,7 +5,6 @@ import (
"fmt"
"net"
"net/url"
"strconv"
"time"
"github.com/Arceliar/phony"
@ -15,7 +14,6 @@ type linkTCP struct {
phony.Inbox
*links
listenconfig *net.ListenConfig
_listeners map[*Listener]context.CancelFunc
}
func (l *links) newLinkTCP() *linkTCP {
@ -24,68 +22,23 @@ func (l *links) newLinkTCP() *linkTCP {
listenconfig: &net.ListenConfig{
KeepAlive: -1,
},
_listeners: map[*Listener]context.CancelFunc{},
}
lt.listenconfig.Control = lt.tcpContext
return lt
}
type tcpDialer struct {
info linkInfo
dialer *net.Dialer
addr *net.TCPAddr
}
func (l *linkTCP) dialersFor(url *url.URL, info linkInfo) ([]*tcpDialer, error) {
host, p, err := net.SplitHostPort(url.Host)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(p)
if err != nil {
return nil, err
}
ips, err := net.LookupIP(host)
if err != nil {
return nil, err
}
dialers := make([]*tcpDialer, 0, len(ips))
for _, ip := range ips {
func (l *linkTCP) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
addr := &net.TCPAddr{
IP: ip,
Port: port,
}
dialer, err := l.dialerFor(addr, info.sintf)
dialer, err := l.tcp.dialerFor(addr, info.sintf)
if err != nil {
continue
return nil, err
}
dialers = append(dialers, &tcpDialer{
info: info,
dialer: dialer,
addr: addr,
})
}
return dialers, nil
}
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
}
if len(dialers) == 0 {
return nil, nil
}
for _, d := range dialers {
var conn net.Conn
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
}
return conn, nil
}
return nil, err
return dialer.DialContext(ctx, "tcp", addr.String())
})
}
func (l *linkTCP) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {

View file

@ -28,6 +28,6 @@ func (t *linkTCP) tcpContext(network, address string, c syscall.RawConn) error {
}
}
func (t *linkTCP) getControl(sintf string) func(string, string, syscall.RawConn) error {
func (t *linkTCP) getControl(_ string) func(string, string, syscall.RawConn) error {
return t.tcpContext
}

View file

@ -13,10 +13,9 @@ import (
type linkTLS struct {
phony.Inbox
*links
tcp *linkTCP
listener *net.ListenConfig
config *tls.Config
_listeners map[*Listener]context.CancelFunc
tcp *linkTCP
listener *net.ListenConfig
config *tls.Config
}
func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS {
@ -27,35 +26,34 @@ func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS {
Control: tcp.tcpContext,
KeepAlive: -1,
},
config: l.core.config.tls.Clone(),
_listeners: map[*Listener]context.CancelFunc{},
config: l.core.config.tls.Clone(),
}
return lt
}
func (l *linkTLS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
dialers, err := l.tcp.dialersFor(url, info)
if err != nil {
return nil, err
}
if len(dialers) == 0 {
return nil, nil
}
for _, d := range dialers {
tlsconfig := l.config.Clone()
tlsconfig.ServerName = options.tlsSNI
tlsconfig := l.config.Clone()
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
tlsconfig.ServerName = hostname
tlsconfig.MinVersion = tls.VersionTLS12
tlsconfig.MaxVersion = tls.VersionTLS13
if sni := options.tlsSNI; sni != "" {
tlsconfig.ServerName = sni
}
addr := &net.TCPAddr{
IP: ip,
Port: port,
}
dialer, err := l.tcp.dialerFor(addr, info.sintf)
if err != nil {
return nil, err
}
tlsdialer := &tls.Dialer{
NetDialer: d.dialer,
NetDialer: dialer,
Config: tlsconfig,
}
var conn net.Conn
conn, err = tlsdialer.DialContext(ctx, "tcp", d.addr.String())
if err != nil {
continue
}
return conn, nil
}
return nil, err
return tlsdialer.DialContext(ctx, "tcp", addr.String())
})
}
func (l *linkTLS) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {

View file

@ -12,9 +12,8 @@ import (
type linkUNIX struct {
phony.Inbox
*links
dialer *net.Dialer
listener *net.ListenConfig
_listeners map[*Listener]context.CancelFunc
dialer *net.Dialer
listener *net.ListenConfig
}
func (l *links) newLinkUNIX() *linkUNIX {
@ -27,7 +26,6 @@ func (l *links) newLinkUNIX() *linkUNIX {
listener: &net.ListenConfig{
KeepAlive: -1,
},
_listeners: map[*Listener]context.CancelFunc{},
}
return lt
}

148
src/core/link_ws.go Normal file
View file

@ -0,0 +1,148 @@
package core
import (
"context"
"fmt"
"net"
"net/http"
"net/url"
"time"
"github.com/Arceliar/phony"
"github.com/coder/websocket"
)
type linkWS struct {
phony.Inbox
*links
listenconfig *net.ListenConfig
}
type linkWSConn struct {
net.Conn
}
type linkWSListener struct {
ch chan *linkWSConn
ctx context.Context
httpServer *http.Server
listener net.Listener
}
type wsServer struct {
ch chan *linkWSConn
ctx context.Context
}
func (l *linkWSListener) Accept() (net.Conn, error) {
qs := <-l.ch
if qs == nil {
return nil, context.Canceled
}
return qs, nil
}
func (l *linkWSListener) Addr() net.Addr {
return l.listener.Addr()
}
func (l *linkWSListener) Close() error {
if err := l.httpServer.Shutdown(l.ctx); err != nil {
return err
}
return l.listener.Close()
}
func (s *wsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/health" || r.URL.Path == "/healthz" {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
return
}
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
Subprotocols: []string{"ygg-ws"},
})
if err != nil {
return
}
if c.Subprotocol() != "ygg-ws" {
c.Close(websocket.StatusPolicyViolation, "client must speak the ygg-ws subprotocol")
return
}
s.ch <- &linkWSConn{
Conn: websocket.NetConn(s.ctx, c, websocket.MessageBinary),
}
}
func (l *links) newLinkWS() *linkWS {
lt := &linkWS{
links: l,
listenconfig: &net.ListenConfig{
KeepAlive: -1,
},
}
return lt
}
func (l *linkWS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
u := *url
u.Host = net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
addr := &net.TCPAddr{
IP: ip,
Port: port,
}
dialer, err := l.tcp.dialerFor(addr, info.sintf)
if err != nil {
return nil, err
}
wsconn, _, err := websocket.Dial(ctx, u.String(), &websocket.DialOptions{
HTTPClient: &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: dialer.Dial,
DialContext: dialer.DialContext,
},
},
Subprotocols: []string{"ygg-ws"},
Host: hostname,
})
if err != nil {
return nil, err
}
return &linkWSConn{
Conn: websocket.NetConn(ctx, wsconn, websocket.MessageBinary),
}, nil
})
}
func (l *linkWS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
nl, err := l.listenconfig.Listen(ctx, "tcp", url.Host)
if err != nil {
return nil, err
}
ch := make(chan *linkWSConn)
httpServer := &http.Server{
Handler: &wsServer{
ch: ch,
ctx: ctx,
},
BaseContext: func(_ net.Listener) context.Context { return ctx },
ReadTimeout: time.Second * 10,
WriteTimeout: time.Second * 10,
}
lwl := &linkWSListener{
ch: ch,
ctx: ctx,
httpServer: httpServer,
listener: nl,
}
go lwl.httpServer.Serve(nl) // nolint:errcheck
return lwl, nil
}

72
src/core/link_wss.go Normal file
View file

@ -0,0 +1,72 @@
package core
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
"github.com/Arceliar/phony"
"github.com/coder/websocket"
)
type linkWSS struct {
phony.Inbox
*links
tlsconfig *tls.Config
}
type linkWSSConn struct {
net.Conn
}
func (l *links) newLinkWSS() *linkWSS {
lwss := &linkWSS{
links: l,
tlsconfig: l.core.config.tls.Clone(),
}
return lwss
}
func (l *linkWSS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
tlsconfig := l.tlsconfig.Clone()
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
tlsconfig.ServerName = hostname
tlsconfig.MinVersion = tls.VersionTLS12
tlsconfig.MaxVersion = tls.VersionTLS13
u := *url
u.Host = net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
addr := &net.TCPAddr{
IP: ip,
Port: port,
}
dialer, err := l.tcp.dialerFor(addr, info.sintf)
if err != nil {
return nil, err
}
wsconn, _, err := websocket.Dial(ctx, u.String(), &websocket.DialOptions{
HTTPClient: &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: dialer.Dial,
DialContext: dialer.DialContext,
TLSClientConfig: tlsconfig,
},
},
Subprotocols: []string{"ygg-ws"},
Host: hostname,
})
if err != nil {
return nil, err
}
return &linkWSSConn{
Conn: websocket.NetConn(ctx, wsconn, websocket.MessageBinary),
}, nil
})
}
func (l *linkWSS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
return nil, fmt.Errorf("WSS listener not supported, use WS listener behind reverse proxy instead")
}

View file

@ -3,6 +3,7 @@ package core
import (
"crypto/ed25519"
"fmt"
"net"
"net/url"
)
@ -13,9 +14,19 @@ func (c *Core) _applyOption(opt SetupOption) (err error) {
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 PeerFilter:
c.config.peerFilter = v
case NodeInfo:
c.config.nodeinfo = v
case NodeInfoPrivacy:
@ -40,9 +51,11 @@ type Peer struct {
type NodeInfo map[string]interface{}
type NodeInfoPrivacy bool
type AllowedPublicKey ed25519.PublicKey
type PeerFilter func(net.IP) bool
func (a ListenAddress) isSetupOption() {}
func (a Peer) isSetupOption() {}
func (a NodeInfo) isSetupOption() {}
func (a NodeInfoPrivacy) isSetupOption() {}
func (a AllowedPublicKey) isSetupOption() {}
func (a PeerFilter) isSetupOption() {}

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

@ -251,15 +251,16 @@ func (p *protoHandler) getSelfHandler(in json.RawMessage) (interface{}, error) {
if kbs, err = hex.DecodeString(req.Key); err != nil {
return nil, err
}
if len(kbs) != ed25519.PublicKeySize {
return nil, fmt.Errorf("invalid public key length")
}
copy(key[:], kbs)
ch := make(chan []byte, 1)
p.sendGetSelfRequest(key, func(info []byte) {
ch <- info
})
timer := time.NewTimer(6 * time.Second)
defer timer.Stop()
select {
case <-timer.C:
case <-time.After(6 * time.Second):
return nil, errors.New("timeout")
case info := <-ch:
var msg json.RawMessage
@ -291,15 +292,16 @@ func (p *protoHandler) getPeersHandler(in json.RawMessage) (interface{}, error)
if kbs, err = hex.DecodeString(req.Key); err != nil {
return nil, err
}
if len(kbs) != ed25519.PublicKeySize {
return nil, fmt.Errorf("invalid public key length")
}
copy(key[:], kbs)
ch := make(chan []byte, 1)
p.sendGetPeersRequest(key, func(info []byte) {
ch <- info
})
timer := time.NewTimer(6 * time.Second)
defer timer.Stop()
select {
case <-timer.C:
case <-time.After(6 * time.Second):
return nil, errors.New("timeout")
case info := <-ch:
ks := make(map[string][]string)
@ -341,15 +343,16 @@ func (p *protoHandler) getTreeHandler(in json.RawMessage) (interface{}, error) {
if kbs, err = hex.DecodeString(req.Key); err != nil {
return nil, err
}
if len(kbs) != ed25519.PublicKeySize {
return nil, fmt.Errorf("invalid public key length")
}
copy(key[:], kbs)
ch := make(chan []byte, 1)
p.sendGetTreeRequest(key, func(info []byte) {
ch <- info
})
timer := time.NewTimer(6 * time.Second)
defer timer.Stop()
select {
case <-timer.C:
case <-time.After(6 * time.Second):
return nil, errors.New("timeout")
case info := <-ch:
ks := make(map[string][]string)

View file

@ -8,7 +8,6 @@ import (
"bytes"
"crypto/ed25519"
"encoding/binary"
"fmt"
"io"
"golang.org/x/crypto/blake2b"
@ -38,6 +37,16 @@ const (
metaPriority // uint8
)
type handshakeError string
func (e handshakeError) Error() string { return string(e) }
const ErrHandshakeInvalidPreamble = handshakeError("invalid handshake, remote side is not Yggdrasil")
const ErrHandshakeInvalidLength = handshakeError("invalid handshake length, possible version mismatch")
const ErrHandshakeInvalidPassword = handshakeError("invalid password supplied, check your config")
const ErrHandshakeHashFailure = handshakeError("invalid hash length")
const ErrHandshakeIncorrectPassword = handshakeError("password does not match remote side")
// Gets a base metadata with no keys set, but with the correct version numbers.
func version_getBaseMetadata() version_metadata {
return version_metadata{
@ -77,7 +86,7 @@ func (m *version_metadata) encode(privateKey ed25519.PrivateKey, password []byte
return nil, err
}
if n != ed25519.PublicKeySize {
return nil, fmt.Errorf("hash writer only wrote %d bytes", n)
return nil, ErrHandshakeHashFailure
}
hash := hasher.Sum(nil)
bs = append(bs, ed25519.Sign(privateKey, hash)...)
@ -87,22 +96,22 @@ func (m *version_metadata) encode(privateKey ed25519.PrivateKey, password []byte
}
// Decodes version metadata from its wire format into the struct.
func (m *version_metadata) decode(r io.Reader, password []byte) bool {
func (m *version_metadata) decode(r io.Reader, password []byte) error {
bh := [6]byte{}
if _, err := io.ReadFull(r, bh[:]); err != nil {
return false
return err
}
meta := [4]byte{'m', 'e', 't', 'a'}
if !bytes.Equal(bh[:4], meta[:]) {
return false
return ErrHandshakeInvalidPreamble
}
bs := make([]byte, binary.BigEndian.Uint16(bh[4:6]))
hl := binary.BigEndian.Uint16(bh[4:6])
if hl < ed25519.SignatureSize {
return ErrHandshakeInvalidLength
}
bs := make([]byte, hl)
if _, err := io.ReadFull(r, bs); err != nil {
return false
}
if len(bs) < ed25519.SignatureSize {
return false
return err
}
sig := bs[len(bs)-ed25519.SignatureSize:]
bs = bs[:len(bs)-ed25519.SignatureSize]
@ -132,14 +141,17 @@ func (m *version_metadata) decode(r io.Reader, password []byte) bool {
hasher, err := blake2b.New512(password)
if err != nil {
return false
return ErrHandshakeInvalidPassword
}
n, err := hasher.Write(m.publicKey)
if err != nil || n != ed25519.PublicKeySize {
return false
return ErrHandshakeHashFailure
}
hash := hasher.Sum(nil)
return ed25519.Verify(m.publicKey, hash, sig)
if !ed25519.Verify(m.publicKey, hash, sig) {
return ErrHandshakeIncorrectPassword
}
return nil
}
// Checks that the "meta" bytes and the version numbers are the expected values.

View file

@ -34,7 +34,7 @@ func TestVersionPasswordAuth(t *testing.T) {
}
var decoded version_metadata
if allowed := decoded.decode(bytes.NewBuffer(encoded), tt.password2); allowed != tt.allowed {
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)
}
}
@ -67,8 +67,8 @@ func TestVersionRoundtrip(t *testing.T) {
}
encoded := bytes.NewBuffer(meta)
decoded := &version_metadata{}
if !decoded.decode(encoded, password) {
t.Fatalf("failed to decode")
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

@ -2,20 +2,47 @@ package multicast
import (
"encoding/json"
"slices"
"strings"
"github.com/Arceliar/phony"
"github.com/yggdrasil-network/yggdrasil-go/src/admin"
)
type GetMulticastInterfacesRequest struct{}
type GetMulticastInterfacesResponse struct {
Interfaces []string `json:"multicast_interfaces"`
Interfaces []MulticastInterfaceState `json:"multicast_interfaces"`
}
func (m *Multicast) getMulticastInterfacesHandler(req *GetMulticastInterfacesRequest, res *GetMulticastInterfacesResponse) error {
res.Interfaces = []string{}
for _, v := range m.Interfaces() {
res.Interfaces = append(res.Interfaces, v.Name)
}
type MulticastInterfaceState struct {
Name string `json:"name"`
Address string `json:"address"`
Beacon bool `json:"beacon"`
Listen bool `json:"listen"`
Password bool `json:"password"`
}
func (m *Multicast) getMulticastInterfacesHandler(_ *GetMulticastInterfacesRequest, res *GetMulticastInterfacesResponse) error {
res.Interfaces = []MulticastInterfaceState{}
phony.Block(m, func() {
for name, intf := range m._interfaces {
is := MulticastInterfaceState{
Name: intf.iface.Name,
Beacon: intf.beacon,
Listen: intf.listen,
Password: len(intf.password) > 0,
}
if li := m._listeners[name]; li != nil && li.listener != nil {
is.Address = li.listener.Addr().String()
} else {
is.Address = "-"
}
res.Interfaces = append(res.Interfaces, is)
}
})
slices.SortStableFunc(res.Interfaces, func(a, b MulticastInterfaceState) int {
return strings.Compare(a.Name, b.Name)
})
return nil
}

View file

@ -9,10 +9,12 @@ import (
"math/rand"
"net"
"net/url"
"sync/atomic"
"time"
"github.com/Arceliar/phony"
"github.com/gologme/log"
"github.com/wlynxg/anet"
"github.com/yggdrasil-network/yggdrasil-go/src/core"
"golang.org/x/crypto/blake2b"
@ -28,7 +30,7 @@ type Multicast struct {
core *core.Core
log *log.Logger
sock *ipv6.PacketConn
_isOpen bool
running atomic.Bool
_listeners map[string]*listenerInfo
_interfaces map[string]*interfaceInfo
_timer *time.Timer
@ -79,7 +81,7 @@ func New(core *core.Core, log *log.Logger, opts ...SetupOption) (*Multicast, err
}
func (m *Multicast) _start() error {
if m._isOpen {
if !m.running.CompareAndSwap(false, true) {
return fmt.Errorf("multicast module is already started")
}
var anyEnabled bool
@ -87,12 +89,14 @@ func (m *Multicast) _start() error {
anyEnabled = anyEnabled || intf.Beacon || intf.Listen
}
if !anyEnabled {
m.running.Store(false)
return nil
}
m.log.Debugln("Starting multicast module")
defer m.log.Debugln("Started multicast module")
addr, err := net.ResolveUDPAddr("udp", string(m.config._groupAddr))
if err != nil {
m.running.Store(false)
return err
}
listenString := fmt.Sprintf("[::]:%v", addr.Port)
@ -101,6 +105,7 @@ func (m *Multicast) _start() error {
}
conn, err := lc.ListenPacket(context.Background(), "udp6", listenString)
if err != nil {
m.running.Store(false)
return err
}
m.sock = ipv6.NewPacketConn(conn)
@ -108,7 +113,6 @@ func (m *Multicast) _start() error {
// Windows can't set this flag, so we need to handle it in other ways
}
m._isOpen = true
go m.listen()
m.Act(nil, m._multicastStarted)
m.Act(nil, m._announce)
@ -118,11 +122,7 @@ func (m *Multicast) _start() error {
// IsStarted returns true if the module has been started.
func (m *Multicast) IsStarted() bool {
var isOpen bool
phony.Block(m, func() {
isOpen = m._isOpen
})
return isOpen
return m.running.Load()
}
// Stop stops the multicast module.
@ -136,8 +136,10 @@ func (m *Multicast) Stop() error {
}
func (m *Multicast) _stop() error {
if !m.running.CompareAndSwap(true, false) {
return nil
}
m.log.Infoln("Stopping multicast module")
m._isOpen = false
if m.sock != nil {
m.sock.Close()
}
@ -147,14 +149,22 @@ func (m *Multicast) _stop() error {
func (m *Multicast) _updateInterfaces() {
interfaces := m._getAllowedInterfaces()
for name, info := range interfaces {
addrs, err := info.iface.Addrs()
// 'anet' package is used here to avoid https://github.com/golang/go/issues/40569
addrs, err := anet.InterfaceAddrsByInterface(&info.iface)
if err != nil {
m.log.Warnf("Failed up get addresses for interface %s: %s", name, err)
delete(interfaces, name)
continue
}
info.addrs = addrs
for _, addr := range addrs {
addrIP, _, err := net.ParseCIDR(addr.String())
if err != nil || addrIP.To4() != nil || !addrIP.IsLinkLocalUnicast() {
continue
}
info.addrs = append(info.addrs, addr)
}
interfaces[name] = info
m.log.Debugf("Discovered addresses for interface %s: %s", name, addrs)
}
m._interfaces = interfaces
}
@ -173,10 +183,11 @@ func (m *Multicast) Interfaces() map[string]net.Interface {
func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo {
interfaces := make(map[string]*interfaceInfo)
// Ask the system for network interfaces
allifaces, err := net.Interfaces()
// 'anet' package is used here to avoid https://github.com/golang/go/issues/40569
allifaces, err := anet.Interfaces()
if err != nil {
// Don't panic, since this may be from e.g. too many open files (from too much connection spam)
// TODO? log something
m.log.Debugf("Failed to get interfaces: %s", err)
return nil
}
// Work out which interfaces to announce on
@ -185,6 +196,8 @@ func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo {
switch {
case iface.Flags&net.FlagUp == 0:
continue // Ignore interfaces that are down
case iface.Flags&net.FlagRunning == 0:
continue // Ignore interfaces that are not running
case iface.Flags&net.FlagMulticast == 0:
continue // Ignore non-multicast interfaces
case iface.Flags&net.FlagPointToPoint != 0:
@ -233,7 +246,7 @@ func (m *Multicast) AnnounceNow() {
}
func (m *Multicast) _announce() {
if !m._isOpen {
if !m.running.Load() {
return
}
m._updateInterfaces()
@ -250,7 +263,7 @@ func (m *Multicast) _announce() {
for name, info := range m._listeners {
// Prepare our stop function!
stop := func() {
info.listener.Close()
info.listener.Cancel()
delete(m._listeners, name)
m.log.Debugln("No longer multicasting on", name)
}
@ -292,13 +305,9 @@ func (m *Multicast) _announce() {
for _, info := range m._interfaces {
iface := info.iface
for _, addr := range info.addrs {
addrIP, _, _ := net.ParseCIDR(addr.String())
// Ignore IPv4 addresses
if addrIP.To4() != nil {
continue
}
// Ignore non-link-local addresses
if !addrIP.IsLinkLocalUnicast() {
addrIP, _, err := net.ParseCIDR(addr.String())
// Ignore IPv4 addresses or non-link-local addresses
if err != nil || addrIP.To4() != nil || !addrIP.IsLinkLocalUnicast() {
continue
}
if info.listen {
@ -320,7 +329,7 @@ func (m *Multicast) _announce() {
Host: net.JoinHostPort(addrIP.String(), fmt.Sprintf("%d", info.port)),
RawQuery: v.Encode(),
}
if li, err := m.core.Listen(u, iface.Name); err == nil {
if li, err := m.core.ListenLocal(u, iface.Name); err == nil {
m.log.Debugln("Started multicasting on", iface.Name)
// Store the listener so that we can stop it later if needed
linfo = &listenerInfo{listener: li, time: time.Now(), port: info.port}
@ -358,6 +367,7 @@ func (m *Multicast) _announce() {
if linfo.interval.Seconds() < 15 {
linfo.interval += time.Second
}
linfo.time = time.Now()
break
}
}
@ -375,6 +385,9 @@ func (m *Multicast) listen() {
bs := make([]byte, 2048)
hb := make([]byte, 0, blake2b.Size) // Reused to reduce hash allocations
for {
if !m.running.Load() {
return
}
n, rcm, fromAddr, err := m.sock.ReadFrom(bs)
if err != nil {
if !m.IsStarted() {

View file

@ -31,7 +31,7 @@ import (
)
func (m *Multicast) _multicastStarted() {
if !m._isOpen {
if !m.running.Load() {
return
}
C.StopAWDLBrowsing()

View file

@ -1,42 +1,70 @@
package tun
const TUN_OFFSET_BYTES = 4
import (
"errors"
wgtun "golang.zx2c4.com/wireguard/tun"
)
const TUN_OFFSET_BYTES = 80 // sizeof(virtio_net_hdr)
func (tun *TunAdapter) read() {
var buf [TUN_OFFSET_BYTES + 65535]byte
vs := tun.iface.BatchSize()
bufs := make([][]byte, vs)
sizes := make([]int, vs)
for i := range bufs {
bufs[i] = make([]byte, TUN_OFFSET_BYTES+65535)
}
for {
n, err := tun.iface.Read(buf[:], TUN_OFFSET_BYTES)
if n <= TUN_OFFSET_BYTES || err != nil {
tun.log.Errorln("Error reading TUN:", err)
ferr := tun.iface.Flush()
if ferr != nil {
tun.log.Errorln("Unable to flush packets:", ferr)
n, err := tun.iface.Read(bufs, sizes, TUN_OFFSET_BYTES)
if err != nil {
if errors.Is(err, wgtun.ErrTooManySegments) {
tun.log.Debugln("TUN segments dropped: %v", err)
continue
}
tun.log.Errorln("Error reading TUN:", err)
return
}
begin := TUN_OFFSET_BYTES
end := begin + n
bs := buf[begin:end]
if _, err := tun.rwc.Write(bs); err != nil {
tun.log.Debugln("Unable to send packet:", err)
for i, b := range bufs[:n] {
if _, err := tun.rwc.Write(b[TUN_OFFSET_BYTES : TUN_OFFSET_BYTES+sizes[i]]); err != nil {
tun.log.Debugln("Unable to send packet:", err)
}
}
}
}
func (tun *TunAdapter) write() {
var buf [TUN_OFFSET_BYTES + 65535]byte
func (tun *TunAdapter) queue() {
for {
bs := buf[TUN_OFFSET_BYTES:]
n, err := tun.rwc.Read(bs)
p := bufPool.Get().([]byte)[:bufPoolSize]
n, err := tun.rwc.Read(p)
if err != nil {
tun.log.Errorln("Exiting tun writer due to core read error:", err)
tun.log.Errorln("Exiting TUN writer due to core read error:", err)
return
}
tun.ch <- p[:n]
}
}
func (tun *TunAdapter) write() {
vs := cap(tun.ch)
bufs := make([][]byte, vs)
for i := range bufs {
bufs[i] = make([]byte, TUN_OFFSET_BYTES+65535)
}
for {
n := len(tun.ch)
if n == 0 {
n = 1 // Nothing queued up yet, wait for it instead
}
for i := 0; i < n; i++ {
msg := <-tun.ch
bufs[i] = append(bufs[i][:TUN_OFFSET_BYTES], msg...)
bufPool.Put(msg) // nolint:staticcheck
}
if !tun.isEnabled {
continue // Nothing to do, the tun isn't enabled
}
bs = buf[:TUN_OFFSET_BYTES+n]
if _, err = tun.iface.Write(bs, TUN_OFFSET_BYTES); err != nil {
if _, err := tun.iface.Write(bufs[:n], TUN_OFFSET_BYTES); err != nil {
tun.Act(nil, func() {
if !tun.isOpen {
tun.log.Errorln("TUN iface write error:", err)

View file

@ -10,9 +10,10 @@ import (
"fmt"
"io"
"net"
"sync"
"github.com/Arceliar/phony"
"golang.zx2c4.com/wireguard/tun"
wgtun "golang.zx2c4.com/wireguard/tun"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
@ -39,7 +40,7 @@ type TunAdapter struct {
addr address.Address
subnet address.Subnet
mtu uint64
iface tun.Device
iface wgtun.Device
phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below
isOpen bool
isEnabled bool // Used by the writer to drop sessionTraffic if not enabled
@ -48,6 +49,7 @@ type TunAdapter struct {
name InterfaceName
mtu InterfaceMTU
}
ch chan []byte
}
// Gets the maximum supported MTU for the platform based on the defaults in
@ -123,6 +125,7 @@ func (tun *TunAdapter) _start() error {
if tun.config.name == "none" || tun.config.name == "dummy" {
tun.log.Debugln("Not starting TUN as ifname is none or dummy")
tun.isEnabled = false
go tun.queue()
go tun.write()
return nil
}
@ -145,6 +148,8 @@ func (tun *TunAdapter) _start() error {
tun.rwc.SetMTU(tun.MTU())
tun.isOpen = true
tun.isEnabled = true
tun.ch = make(chan []byte, tun.iface.BatchSize())
go tun.queue()
go tun.read()
go tun.write()
return nil
@ -178,3 +183,12 @@ func (tun *TunAdapter) _stop() error {
}
return nil
}
const bufPoolSize = TUN_OFFSET_BYTES + 65535
var bufPool = sync.Pool{
New: func() any {
b := [bufPoolSize]byte{}
return b[:]
},
}

View file

@ -7,6 +7,7 @@ package tun
import (
"encoding/binary"
"fmt"
"os"
"strconv"
"strings"
@ -24,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 {
@ -42,17 +43,17 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
dfd, err := unix.Dup(int(fd))
if err != nil {
return err
return fmt.Errorf("failed to duplicate FD: %w", err)
}
err = unix.SetNonblock(dfd, true)
if err != nil {
unix.Close(dfd)
return err
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 err
return fmt.Errorf("failed to create TUN from FD: %w", err)
}
tun.iface = iface
if m, err := iface.MTU(); err == nil {
@ -110,8 +111,8 @@ func (tun *TunAdapter) setupAddress(addr string) error {
var err 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
tun.log.Errorf("Create AF_SYSTEM socket failed: %v.", err)
return fmt.Errorf("failed to open AF_SYSTEM: %w", err)
}
var ar in6_aliasreq
@ -149,13 +150,13 @@ func (tun *TunAdapter) setupAddress(addr string) error {
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 { // 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,5 +1,5 @@
//go:build openbsd || freebsd
// +build openbsd freebsd
//go:build freebsd
// +build freebsd
package tun
@ -54,11 +54,6 @@ struct in6_ifreq {
290 };
*/
type in6_ifreq_mtu struct {
ifr_name [syscall.IFNAMSIZ]byte
ifru_mtu int
}
type in6_ifreq_addr struct {
ifr_name [syscall.IFNAMSIZ]byte
ifru_addr sockaddr_in6
@ -78,7 +73,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 {
@ -112,26 +107,6 @@ func (tun *TunAdapter) setupAddress(addr string) error {
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
// Create the MTU request
var ir in6_ifreq_mtu
copy(ir.ifr_name[:], tun.Name())
ir.ifru_mtu = int(tun.mtu)
// Set the MTU
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
err = errno
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
// Fall back to ifconfig to set the MTU
cmd := exec.Command("ifconfig", tun.Name(), "mtu", string(tun.mtu))
tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err)
tun.log.Traceln(string(output))
}
}
// Create the address request
// FIXME: I don't work!
var ar in6_ifreq_addr

View file

@ -19,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 {
@ -45,20 +45,20 @@ func (tun *TunAdapter) setupFD(fd int32, 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())

122
src/tun/tun_openbsd.go Normal file
View file

@ -0,0 +1,122 @@
//go:build openbsd
// +build openbsd
package tun
import (
"fmt"
"net"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
wgtun "golang.zx2c4.com/wireguard/tun"
)
const (
SIOCAIFADDR_IN6 = 0x8080691a
ND6_INFINITE_LIFETIME = 0xffffffff
)
type in6_addrlifetime struct {
ia6t_expire int64
ia6t_preferred int64
ia6t_vltime uint32
ia6t_pltime uint32
}
// Match types from the net package, effectively being [16]byte for IPv6 addresses.
type in6_addr [16]uint8
type sockaddr_in6 struct {
sin6_len uint8
sin6_family uint8
sin6_port uint16
sin6_flowinfo uint32
sin6_addr in6_addr
sin6_scope_id uint32
}
func (sa6 *sockaddr_in6) setSockaddr(addr [/*16*/]byte /* net.IP or net.IPMask */) {
sa6.sin6_len = uint8(unsafe.Sizeof(*sa6))
sa6.sin6_family = unix.AF_INET6
for i := range sa6.sin6_addr {
sa6.sin6_addr[i] = addr[i]
}
}
type in6_aliasreq struct {
ifra_name [syscall.IFNAMSIZ]byte
ifra_addr sockaddr_in6
ifra_dstaddr sockaddr_in6
ifra_prefixmask sockaddr_in6
ifra_flags int32
ifra_lifetime in6_addrlifetime
}
// Configures the TUN adapter with the correct IPv6 address and MTU.
func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
iface, err := wgtun.CreateTUN(ifname, int(mtu))
if err != nil {
return fmt.Errorf("failed to create TUN: %w", err)
}
tun.iface = iface
if mtu, err := iface.MTU(); err == nil {
tun.mtu = getSupportedMTU(uint64(mtu))
} else {
tun.mtu = 0
}
if addr != "" {
return tun.setupAddress(addr)
}
return nil
}
// Configures the "utun" adapter from an existing file descriptor.
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
return fmt.Errorf("setup via FD not supported on this platform")
}
func (tun *TunAdapter) setupAddress(addr string) error {
var sfd int
var err error
ip, prefix, err := net.ParseCIDR(addr)
if err != nil {
tun.log.Errorf("Error in ParseCIDR: %v", err)
return err
}
// Create system socket
if sfd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0); err != nil {
tun.log.Printf("Create AF_INET6 socket failed: %v", err)
return err
}
// Friendly output
tun.log.Infof("Interface name: %s", tun.Name())
tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu)
// Create the address request
var ar in6_aliasreq
copy(ar.ifra_name[:], tun.Name())
ar.ifra_addr.setSockaddr(ip)
prefixmask := net.CIDRMask(prefix.Mask.Size())
ar.ifra_prefixmask.setSockaddr(prefixmask)
ar.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME
ar.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME
// Set the interface address
if err = unix.IoctlSetInt(sfd, SIOCAIFADDR_IN6, int(uintptr(unsafe.Pointer(&ar)))); err != nil {
tun.log.Errorf("Error in SIOCAIFADDR_IN6: %v", err)
return err
}
return nil
}

View file

@ -16,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 {

View file

@ -8,10 +8,12 @@ import (
"fmt"
"log"
"net/netip"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wintun"
wgtun "golang.zx2c4.com/wireguard/tun"
"golang.zx2c4.com/wireguard/windows/elevate"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
@ -31,11 +33,23 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
if guid, err = windows.GUIDFromString("{8f59971a-7872-4aa6-b2eb-061fc4e9d0a7}"); err != nil {
return err
}
if iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu)); err != nil {
return err
iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu))
if err != nil {
// Very rare condition, it will purge the old device and create new
tun.log.Printf("Error creating TUN: '%s'", err)
wintun.Uninstall()
time.Sleep(3 * time.Second)
tun.log.Printf("Trying again")
iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu))
if err != nil {
return err
}
}
tun.log.Printf("Waiting for TUN to come up")
time.Sleep(1 * time.Second)
tun.iface = iface
if addr != "" {
tun.log.Printf("Setting up address")
if err = tun.setupAddress(addr); err != nil {
tun.log.Errorln("Failed to set up TUN address:", err)
return err
@ -48,6 +62,7 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
if mtu, err := iface.MTU(); err == nil {
tun.mtu = uint64(mtu)
}
tun.log.Printf("TUN is set up successfully")
return nil
})
}