Compare commits

...

240 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
Neil Alexander
90c6288f7c
Yggdrasil 0.5 RC3 2023-10-23 22:26:53 +01:00
Neil Alexander
094f80f39c
Fix RetryPeersNow, move startup logging, don't set TUN address if not available 2023-10-22 15:51:30 +01:00
Neil Alexander
955aa4af79
Remove unnecessary pprof log line 2023-10-22 10:29:19 +01:00
Neil Alexander
73c6c25bd9
Restore removePeer method 2023-10-22 10:27:41 +01:00
Neil Alexander
80e56eafcd
Allow PPROFLISTEN on all builds 2023-10-21 21:36:28 +01:00
Alex Akselrod
6a9493757d
mobile: add support for Listen in config (#1063)
Co-authored-by: Neil <git@neilalexander.dev>
2023-10-21 17:33:17 +00:00
John Jolly
8ea20cd205 Add output for threadcount and key generation time to cmd/genkey
This change is to display information about the key generation process.

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

Signed-off-by: Oleksandr Natalenko <oleksandr@natalenko.name>
2023-06-08 21:44:46 +02:00
Neil Alexander
db9b57c052
Update contrib/mobile for the latest iOS build 2023-06-06 22:11:49 +01:00
Neil Alexander
2eda59d9e4
Improve link setup locking and guards 2023-05-23 22:39:10 +01:00
Neil Alexander
06ca8941c7
Fix race condition between incoming and outgoing connection setup 2023-05-22 23:10:44 +01:00
Arceliar
8562b6b86e
Merge pull request #1040 from yggdrasil-network/Arceliar/allocs
Reduce allocations
2023-05-21 12:56:37 -05:00
Arceliar
e94985c583 try to cheer up the linter again 2023-05-21 12:49:49 -05:00
Arceliar
5a6f27e732 cheer up the linter 2023-05-21 12:43:03 -05:00
Arceliar
8b5add5301 reduce allocations (also pulls in updated ironwood to do the same) 2023-05-21 12:38:16 -05:00
Neil
52709696a5
Merge pull request #1036 from yggdrasil-network/neil/linktweaks
Tweak link state locking, add comments, listener priority, other fixes
2023-05-21 00:06:43 +01:00
Neil Alexander
cb8333f9ff
Tweak lock behaviour 2023-05-21 00:02:04 +01:00
Neil Alexander
333561f4e1
Tweak link state locking, add comments, listener priority, other fixes 2023-05-20 23:44:31 +01:00
Neil
2565cbf11b
Merge pull request #1034 from yggdrasil-network/neil/futurelink2
Link refactoring, admin socket changes, TLS changes
2023-05-20 23:02:44 +01:00
Arceliar
19ca25538f
Merge pull request #1033 from yggdrasil-network/ironwood-experimental
Update to experimental ironwood
2023-05-20 17:00:23 -05:00
Neil Alexander
aff3201084
Fix incoming connection handlers 2023-05-20 22:22:15 +01:00
Neil Alexander
c0188f5600
Discriminate multicast peers more loosely 2023-05-20 21:18:49 +01:00
Neil Alexander
e0b39b303f
Use regular mutex instead (less type assertions)
This reverts commit 5ba9dadc49.
2023-05-20 18:36:44 +01:00
Neil Alexander
5ba9dadc49
Use sync.Map instead of link actor 2023-05-20 18:31:01 +01:00
Neil Alexander
6e338b6f89
Fix con urrent map accesses 2023-05-20 18:21:02 +01:00
Neil Alexander
e290e744f4
Fix -autoconf 2023-05-20 10:54:49 +01:00
Neil Alexander
a233e775eb
yggdrasilctl tweaks 2023-05-19 20:57:14 +01:00
Neil Alexander
6ac2fae845
Fix Windows build 2023-05-19 20:34:51 +01:00
Neil Alexander
7b1635245f
Add missing path notify and bloom transform 2023-05-19 19:33:40 +01:00
Neil Alexander
a9ec3877b5
Fix unit test 2023-05-19 19:09:06 +01:00
Neil Alexander
7afa23be4c
Link refactoring, admin socket changes 2023-05-19 19:09:05 +01:00
Arceliar
c7ee7d9681 update ironwood dependency (it should build now...) 2023-05-14 21:24:08 -05:00
Arceliar
ffb2f06992 Merge branch 'ironwood-experimental' of https://github.com/yggdrasil-network/yggdrasil-go into ironwood-experimental 2023-05-14 21:14:32 -05:00
Arceliar
101189a9dc update ironwood dependency 2023-05-14 21:13:53 -05:00
Neil Alexander
c7ea223a9a
Update mobile bindings 2023-05-14 10:16:33 +01:00
Arceliar
669e61af9a update to bugfixed ironwood, fix broken core test, add getPaths handler to admin socket 2023-05-13 16:15:04 -05:00
Arceliar
5e95246c26 update to ironwood v0.0.0-20230513191034-495699d87ae4 with API changes 2023-05-13 14:44:38 -05:00
Neil Alexander
1345960d5f
Update to Arceliar/ironwood@14d951a 2023-05-07 17:29:46 +01:00
Arceliar
8696650958
Update go.mod 2023-03-26 17:06:18 -05:00
Arceliar
ebd3596c2c
Update ci.yml 2023-03-26 17:05:55 -05:00
Arceliar
e99c870d51 update admin functions and fix core tests 2023-03-26 16:49:40 -05:00
Arceliar
abbe94fa80 fix core tests and run gofmt on src 2023-03-26 16:34:49 -05:00
Arceliar
fc632c5caa comment out some unused ipv6rwc code 2023-03-26 16:17:31 -05:00
Arceliar
5b6d9d52f3 update ironwood replace, update ipv6rwc to work (may need updates later if interface changes) 2023-03-26 16:12:45 -05:00
Neil Alexander
5a243d5b95
Update ironwood replace 2023-03-19 21:44:34 +00:00
Neil Alexander
a148f4cfec
More updates for Ygg v0.5 2023-03-19 10:33:07 +00:00
Neil Alexander
83c1a810b5
New handshake, use softcrdt upstream 2023-03-18 12:14:32 +00:00
Neil
1420ea5662
Merge pull request #1004 from Dry-Leaf/bsd_build_fix
Added member to Logger struct expected by tun_bsd.go
2023-02-26 22:01:32 +00:00
Neil
a8f0ada7ee
Merge branch 'develop' into bsd_build_fix 2023-02-26 21:54:50 +00:00
Neil
1685b87a04
Merge pull request #1021 from yggdrasil-network/dependabot/go_modules/golang.org/x/net-0.7.0
Bump golang.org/x/net from 0.0.0-20221014081412-f15817d10f9b to 0.7.0
2023-02-26 21:43:51 +00:00
Neil
9ee6c46b1d
Merge branch 'develop' into bsd_build_fix 2023-02-26 21:40:29 +00:00
Neil
3b0a819e68
Merge branch 'develop' into dependabot/go_modules/golang.org/x/net-0.7.0 2023-02-26 21:36:09 +00:00
Neil Alexander
38736358dd
Fix lint error properly this time 2023-02-26 21:35:56 +00:00
Neil Alexander
1dd1d0ab8c
Build packages with Go 1.20 2023-02-26 21:32:26 +00:00
Neil Alexander
6d6c408957
Test against Go 1.20, maybe fix lint issue 2023-02-26 21:31:20 +00:00
Neil
783b4d3de6
Merge branch 'develop' into bsd_build_fix 2023-02-26 21:28:29 +00:00
Neil
a6f742ee93
Merge branch 'develop' into dependabot/go_modules/golang.org/x/net-0.7.0 2023-02-26 21:27:56 +00:00
Neil
4189053cfc
Merge pull request #981 from yggdrasil-network/neilalexander/tryall
Try all addresses when connecting to a DNS name
2023-02-26 21:24:01 +00:00
dependabot[bot]
886281af7c
Bump golang.org/x/net from 0.0.0-20221014081412-f15817d10f9b to 0.7.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20221014081412-f15817d10f9b to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/commits/v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-25 02:28:24 +00:00
anon
9cbc71bc8a Added member to Logger struct expected by tun_bsd.go 2022-12-18 00:37:34 -05:00
Neil Alexander
723097fbf6
Deduplicate some logic 2022-11-26 16:18:15 +00:00
Neil Alexander
1adc88ec77
Merge branch 'develop' into neilalexander/tryall 2022-11-26 16:00:46 +00:00
Neil Alexander
110613b234 Try all addresses when connecting to a DNS name
Fixes #980
2022-11-08 21:59:13 +00:00
88 changed files with 4477 additions and 2394 deletions

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

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 If you want to build from source, as opposed to installing one of the pre-built
packages: packages:
1. Install [Go](https://golang.org) (requires Go 1.17 or later) 1. Install [Go](https://golang.org) (requires Go 1.22 or later)
2. Clone this repository 2. Clone this repository
2. Run `./build` 2. Run `./build`

View file

@ -1,5 +1,4 @@
/* /*
This file generates crypto keys. This file generates crypto keys.
It prints out a new set of keys each time if finds a "better" one. It prints out a new set of keys each time if finds a "better" one.
By default, "better" means a higher NodeID (-> higher IP address). By default, "better" means a higher NodeID (-> higher IP address).
@ -8,7 +7,6 @@ This is because the IP address format can compress leading 1s in the address, to
If run with the "-sig" flag, it generates signing keys instead. If run with the "-sig" flag, it generates signing keys instead.
A "better" signing key means one with a higher TreeID. A "better" signing key means one with a higher TreeID.
This only matters if it's high enough to make you the root of the tree. This only matters if it's high enough to make you the root of the tree.
*/ */
package main package main
@ -18,6 +16,9 @@ import (
"fmt" "fmt"
"net" "net"
"runtime" "runtime"
"time"
"suah.dev/protect"
"github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/address"
) )
@ -25,10 +26,19 @@ import (
type keySet struct { type keySet struct {
priv ed25519.PrivateKey priv ed25519.PrivateKey
pub ed25519.PublicKey pub ed25519.PublicKey
count uint64
} }
func main() { func main() {
if err := protect.Pledge("stdio"); err != nil {
panic(err)
}
threads := runtime.GOMAXPROCS(0) threads := runtime.GOMAXPROCS(0)
fmt.Println("Threads:", threads)
start := time.Now()
var totalKeys uint64
totalKeys = 0
var currentBest ed25519.PublicKey var currentBest ed25519.PublicKey
newKeys := make(chan keySet, threads) newKeys := make(chan keySet, threads)
for i := 0; i < threads; i++ { for i := 0; i < threads; i++ {
@ -37,8 +47,9 @@ func main() {
for { for {
newKey := <-newKeys newKey := <-newKeys
if isBetter(currentBest, newKey.pub) || len(currentBest) == 0 { if isBetter(currentBest, newKey.pub) || len(currentBest) == 0 {
totalKeys += newKey.count
currentBest = newKey.pub currentBest = newKey.pub
fmt.Println("-----") fmt.Println("-----", time.Since(start), "---", totalKeys, "keys tried")
fmt.Println("Priv:", hex.EncodeToString(newKey.priv)) fmt.Println("Priv:", hex.EncodeToString(newKey.priv))
fmt.Println("Pub:", hex.EncodeToString(newKey.pub)) fmt.Println("Pub:", hex.EncodeToString(newKey.pub))
addr := address.AddrForKey(newKey.pub) addr := address.AddrForKey(newKey.pub)
@ -61,11 +72,14 @@ func isBetter(oldPub, newPub ed25519.PublicKey) bool {
func doKeys(out chan<- keySet) { func doKeys(out chan<- keySet) {
bestKey := make(ed25519.PublicKey, ed25519.PublicKeySize) bestKey := make(ed25519.PublicKey, ed25519.PublicKeySize)
var count uint64
count = 0
for idx := range bestKey { for idx := range bestKey {
bestKey[idx] = 0xff bestKey[idx] = 0xff
} }
for { for {
pub, priv, err := ed25519.GenerateKey(nil) pub, priv, err := ed25519.GenerateKey(nil)
count++
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -73,6 +87,7 @@ func doKeys(out chan<- keySet) {
continue continue
} }
bestKey = pub bestKey = pub
out <- keySet{priv, pub} out <- keySet{priv, pub, count}
count = 0
} }
} }

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

@ -1,34 +1,29 @@
package main package main
import ( import (
"bytes"
"context" "context"
"crypto/ed25519" "crypto/ed25519"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"io"
"net" "net"
"os" "os"
"os/signal" "os/signal"
"regexp" "regexp"
"strings" "strings"
"sync"
"syscall" "syscall"
"golang.org/x/text/encoding/unicode" "suah.dev/protect"
"github.com/gologme/log" "github.com/gologme/log"
gsyslog "github.com/hashicorp/go-syslog" gsyslog "github.com/hashicorp/go-syslog"
"github.com/hjson/hjson-go" "github.com/hjson/hjson-go/v4"
"github.com/kardianos/minwinsvc" "github.com/kardianos/minwinsvc"
"github.com/mitchellh/mapstructure"
"github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/admin"
"github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc"
"github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/core"
@ -44,75 +39,302 @@ type node struct {
admin *admin.AdminSocket admin *admin.AdminSocket
} }
func readConfig(log *log.Logger, useconf bool, useconffile string, normaliseconf bool) *config.NodeConfig { // The main function is responsible for configuring and starting Yggdrasil.
// Use a configuration file. If -useconf, the configuration will be read func main() {
// from stdin. If -useconffile, the configuration will be read from the // Not all operations are coverable with pledge(2), so immediately
// filesystem. // limit file system access with unveil(2), effectively preventing
var conf []byte // "proc exec" promises right from the start:
var err error //
if useconffile != "" { // - read arbitrary config file
// Read the file from the filesystem // - create/write arbitrary log file
conf, err = os.ReadFile(useconffile) // - read/write/chmod/remove admin socket, if at all
if err := protect.Unveil("/", "rwc"); err != nil {
panic(fmt.Sprintf("unveil: / rwc: %v", err))
}
if err := protect.UnveilBlock(); err != nil {
panic(fmt.Sprintf("unveil: %v", err))
}
genconf := flag.Bool("genconf", false, "print a new config to stdout")
useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin")
useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path")
normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised")
exportkey := flag.Bool("exportkey", false, "use in combination with either -useconf or -useconffile, outputs your private key in PEM format")
confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON")
autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)")
ver := flag.Bool("version", false, "prints the version of this build")
logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"")
getaddr := flag.Bool("address", false, "use in combination with either -useconf or -useconffile, outputs your IPv6 address")
getsnet := flag.Bool("subnet", false, "use in combination with either -useconf or -useconffile, outputs your IPv6 subnet")
getpkey := flag.Bool("publickey", false, "use in combination with either -useconf or -useconffile, outputs your public key")
loglevel := flag.String("loglevel", "info", "loglevel to enable")
chuserto := flag.String("user", "", "user (and, optionally, group) to set UID/GID to")
flag.Parse()
done := make(chan struct{})
defer close(done)
// Catch interrupts from the operating system to exit gracefully.
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
// Create a new logger that logs output to stdout.
var logger *log.Logger
switch *logto {
case "stdout":
logger = log.New(os.Stdout, "", log.Flags())
case "syslog":
if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", version.BuildName()); err == nil {
logger = log.New(syslogger, "", log.Flags()&^(log.Ldate|log.Ltime))
}
default:
if logfd, err := os.OpenFile(*logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil {
logger = log.New(logfd, "", log.Flags())
}
}
if logger == nil {
logger = log.New(os.Stdout, "", log.Flags())
logger.Warnln("Logging defaulting to stdout")
}
if *normaliseconf {
setLogLevel("error", logger)
} else { } else {
// Read the file from stdin. setLogLevel(*loglevel, logger)
conf, err = io.ReadAll(os.Stdin)
} }
if err != nil {
panic(err) cfg := config.GenerateConfig()
var err error
switch {
case *ver:
fmt.Println("Build name:", version.BuildName())
fmt.Println("Build version:", version.BuildVersion())
return
case *autoconf:
// Use an autoconf-generated config, this will give us random keys and
// port numbers, and will use an automatically selected TUN interface.
case *useconf:
if _, err := cfg.ReadFrom(os.Stdin); err != nil {
panic(err)
}
case *useconffile != "":
f, err := os.Open(*useconffile)
if err != nil {
panic(err)
}
if _, err := cfg.ReadFrom(f); err != nil {
panic(err)
}
_ = f.Close()
case *genconf:
cfg.AdminListen = ""
var bs []byte
if *confjson {
bs, err = json.MarshalIndent(cfg, "", " ")
} else {
bs, err = hjson.Marshal(cfg)
}
if err != nil {
panic(err)
}
fmt.Println(string(bs))
return
default:
fmt.Println("Usage:")
flag.PrintDefaults()
if *getaddr || *getsnet {
fmt.Println("\nError: You need to specify some config data using -useconf or -useconffile.")
}
return
} }
// If there's a byte order mark - which Windows 10 is now incredibly fond of
// throwing everywhere when it's converting things into UTF-16 for the hell privateKey := ed25519.PrivateKey(cfg.PrivateKey)
// of it - remove it and decode back down into UTF-8. This is necessary publicKey := privateKey.Public().(ed25519.PublicKey)
// because hjson doesn't know what to do with UTF-16 and will panic
if bytes.Equal(conf[0:2], []byte{0xFF, 0xFE}) || switch {
bytes.Equal(conf[0:2], []byte{0xFE, 0xFF}) { case *getaddr:
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) addr := address.AddrForKey(publicKey)
decoder := utf.NewDecoder() ip := net.IP(addr[:])
conf, err = decoder.Bytes(conf) fmt.Println(ip.String())
return
case *getsnet:
snet := address.SubnetForKey(publicKey)
ipnet := net.IPNet{
IP: append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0),
Mask: net.CIDRMask(len(snet)*8, 128),
}
fmt.Println(ipnet.String())
return
case *getpkey:
fmt.Println(hex.EncodeToString(publicKey))
return
case *normaliseconf:
cfg.AdminListen = ""
if cfg.PrivateKeyPath != "" {
cfg.PrivateKey = nil
}
var bs []byte
if *confjson {
bs, err = json.MarshalIndent(cfg, "", " ")
} else {
bs, err = hjson.Marshal(cfg)
}
if err != nil {
panic(err)
}
fmt.Println(string(bs))
return
case *exportkey:
pem, err := cfg.MarshalPEMPrivateKey()
if err != nil {
panic(err)
}
fmt.Println(string(pem))
return
}
n := &node{}
// Set up the Yggdrasil node itself.
{
iprange := net.IPNet{
IP: net.ParseIP("200::"),
Mask: net.CIDRMask(7, 128),
}
options := []core.SetupOption{
core.NodeInfo(cfg.NodeInfo),
core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
core.PeerFilter(func(ip net.IP) bool {
return !iprange.Contains(ip)
}),
}
for _, addr := range cfg.Listen {
options = append(options, core.ListenAddress(addr))
}
for _, peer := range cfg.Peers {
options = append(options, core.Peer{URI: peer})
}
for intf, peers := range cfg.InterfacePeers {
for _, peer := range peers {
options = append(options, core.Peer{URI: peer, SourceInterface: intf})
}
}
for _, allowed := range cfg.AllowedPublicKeys {
k, err := hex.DecodeString(allowed)
if err != nil {
panic(err)
}
options = append(options, core.AllowedPublicKey(k[:]))
}
if n.core, err = core.New(cfg.Certificate, logger, options...); err != nil {
panic(err)
}
address, subnet := n.core.Address(), n.core.Subnet()
logger.Printf("Your public key is %s", hex.EncodeToString(n.core.PublicKey()))
logger.Printf("Your IPv6 address is %s", address.String())
logger.Printf("Your IPv6 subnet is %s", subnet.String())
}
// Set up the admin socket.
{
options := []admin.SetupOption{
admin.ListenAddress(cfg.AdminListen),
}
if cfg.LogLookups {
options = append(options, admin.LogLookups{})
}
if n.admin, err = admin.New(n.core, logger, options...); err != nil {
panic(err)
}
if n.admin != nil {
n.admin.SetupAdminHandlers()
}
}
// Set up the multicast module.
{
options := []multicast.SetupOption{}
for _, intf := range cfg.MulticastInterfaces {
options = append(options, multicast.MulticastInterface{
Regex: regexp.MustCompile(intf.Regex),
Beacon: intf.Beacon,
Listen: intf.Listen,
Port: intf.Port,
Priority: uint8(intf.Priority),
Password: intf.Password,
})
}
if n.multicast, err = multicast.New(n.core, logger, options...); err != nil {
panic(err)
}
if n.admin != nil && n.multicast != nil {
n.multicast.SetupAdminHandlers(n.admin)
}
}
// Set up the TUN module.
{
options := []tun.SetupOption{
tun.InterfaceName(cfg.IfName),
tun.InterfaceMTU(cfg.IfMTU),
}
if n.tun, err = tun.New(ipv6rwc.NewReadWriteCloser(n.core), logger, options...); err != nil {
panic(err)
}
if n.admin != nil && n.tun != nil {
n.tun.SetupAdminHandlers(n.admin)
}
}
//Windows service shutdown
minwinsvc.SetOnExit(func() {
logger.Infof("Shutting down service ...")
cancel()
// Wait for all parts to shutdown properly
<-done
})
// Change user if requested
if *chuserto != "" {
err = chuser(*chuserto)
if err != nil { if err != nil {
panic(err) panic(err)
} }
} }
// Generate a new configuration - this gives us a set of sane defaults -
// then parse the configuration we loaded above on top of it. The effect
// of this is that any configuration item that is missing from the provided
// configuration will use a sane default.
cfg := defaults.GenerateConfig()
var dat map[string]interface{}
if err := hjson.Unmarshal(conf, &dat); err != nil {
panic(err)
}
// Sanitise the config
confJson, err := json.Marshal(dat)
if err != nil {
panic(err)
}
if err := json.Unmarshal(confJson, &cfg); err != nil {
panic(err)
}
// Overlay our newly mapped configuration onto the autoconf node config that
// we generated above.
if err = mapstructure.Decode(dat, &cfg); err != nil {
panic(err)
}
return cfg
}
// Generates a new configuration and returns it in HJSON format. This is used // Promise final modes of operation. At this point, if at all:
// with -genconf. // - raw socket is created/open
func doGenconf(isjson bool) string { // - admin socket is created/open
cfg := defaults.GenerateConfig() // - privileges are dropped to non-root user
var bs []byte //
var err error // Peers, InterfacePeers, Listen can be UNIX sockets;
if isjson { // Go's net.Listen.Close() deletes files on shutdown.
bs, err = json.MarshalIndent(cfg, "", " ") promises := []string{"stdio", "cpath", "inet", "unix", "dns"}
} else { if len(cfg.MulticastInterfaces) > 0 {
bs, err = hjson.Marshal(cfg) promises = append(promises, "mcast")
} }
if err != nil { if err := protect.Pledge(strings.Join(promises, " ")); err != nil {
panic(err) panic(fmt.Sprintf("pledge: %v: %v", promises, err))
} }
return string(bs)
// Block until we are told to shut down.
<-ctx.Done()
// Shut down the node.
_ = n.admin.Stop()
_ = n.multicast.Stop()
_ = n.tun.Stop()
n.core.Stop()
} }
func setLogLevel(loglevel string, logger *log.Logger) { func setLogLevel(loglevel string, logger *log.Logger) {
@ -140,269 +362,3 @@ func setLogLevel(loglevel string, logger *log.Logger) {
} }
} }
} }
type yggArgs struct {
genconf bool
useconf bool
normaliseconf bool
confjson bool
autoconf bool
ver bool
getaddr bool
getsnet bool
useconffile string
logto string
loglevel string
}
func getArgs() yggArgs {
genconf := flag.Bool("genconf", false, "print a new config to stdout")
useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin")
useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path")
normaliseconf := flag.Bool("normaliseconf", false, "use in combination with either -useconf or -useconffile, outputs your configuration normalised")
confjson := flag.Bool("json", false, "print configuration from -genconf or -normaliseconf as JSON instead of HJSON")
autoconf := flag.Bool("autoconf", false, "automatic mode (dynamic IP, peer with IPv6 neighbors)")
ver := flag.Bool("version", false, "prints the version of this build")
logto := flag.String("logto", "stdout", "file path to log to, \"syslog\" or \"stdout\"")
getaddr := flag.Bool("address", false, "returns the IPv6 address as derived from the supplied configuration")
getsnet := flag.Bool("subnet", false, "returns the IPv6 subnet as derived from the supplied configuration")
loglevel := flag.String("loglevel", "info", "loglevel to enable")
flag.Parse()
return yggArgs{
genconf: *genconf,
useconf: *useconf,
useconffile: *useconffile,
normaliseconf: *normaliseconf,
confjson: *confjson,
autoconf: *autoconf,
ver: *ver,
logto: *logto,
getaddr: *getaddr,
getsnet: *getsnet,
loglevel: *loglevel,
}
}
// The main function is responsible for configuring and starting Yggdrasil.
func run(args yggArgs, ctx context.Context) {
// Create a new logger that logs output to stdout.
var logger *log.Logger
switch args.logto {
case "stdout":
logger = log.New(os.Stdout, "", log.Flags())
case "syslog":
if syslogger, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, "DAEMON", version.BuildName()); err == nil {
logger = log.New(syslogger, "", log.Flags())
}
default:
if logfd, err := os.OpenFile(args.logto, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil {
logger = log.New(logfd, "", log.Flags())
}
}
if logger == nil {
logger = log.New(os.Stdout, "", log.Flags())
logger.Warnln("Logging defaulting to stdout")
}
if args.normaliseconf {
setLogLevel("error", logger)
} else {
setLogLevel(args.loglevel, logger)
}
var cfg *config.NodeConfig
var err error
switch {
case args.ver:
fmt.Println("Build name:", version.BuildName())
fmt.Println("Build version:", version.BuildVersion())
return
case args.autoconf:
// Use an autoconf-generated config, this will give us random keys and
// port numbers, and will use an automatically selected TUN interface.
cfg = defaults.GenerateConfig()
case args.useconffile != "" || args.useconf:
// Read the configuration from either stdin or from the filesystem
cfg = readConfig(logger, args.useconf, args.useconffile, args.normaliseconf)
// If the -normaliseconf option was specified then remarshal the above
// configuration and print it back to stdout. This lets the user update
// their configuration file with newly mapped names (like above) or to
// convert from plain JSON to commented HJSON.
if args.normaliseconf {
var bs []byte
if args.confjson {
bs, err = json.MarshalIndent(cfg, "", " ")
} else {
bs, err = hjson.Marshal(cfg)
}
if err != nil {
panic(err)
}
fmt.Println(string(bs))
return
}
case args.genconf:
// Generate a new configuration and print it to stdout.
fmt.Println(doGenconf(args.confjson))
return
default:
// No flags were provided, therefore print the list of flags to stdout.
fmt.Println("Usage:")
flag.PrintDefaults()
if args.getaddr || args.getsnet {
fmt.Println("\nError: You need to specify some config data using -useconf or -useconffile.")
}
}
// Have we got a working configuration? If we don't then it probably means
// that neither -autoconf, -useconf or -useconffile were set above. Stop
// if we don't.
if cfg == nil {
return
}
// Have we been asked for the node address yet? If so, print it and then stop.
getNodeKey := func() ed25519.PublicKey {
if pubkey, err := hex.DecodeString(cfg.PrivateKey); err == nil {
return ed25519.PrivateKey(pubkey).Public().(ed25519.PublicKey)
}
return nil
}
switch {
case args.getaddr:
if key := getNodeKey(); key != nil {
addr := address.AddrForKey(key)
ip := net.IP(addr[:])
fmt.Println(ip.String())
}
return
case args.getsnet:
if key := getNodeKey(); key != nil {
snet := address.SubnetForKey(key)
ipnet := net.IPNet{
IP: append(snet[:], 0, 0, 0, 0, 0, 0, 0, 0),
Mask: net.CIDRMask(len(snet)*8, 128),
}
fmt.Println(ipnet.String())
}
return
}
n := &node{}
// Setup the Yggdrasil node itself.
{
sk, err := hex.DecodeString(cfg.PrivateKey)
if err != nil {
panic(err)
}
options := []core.SetupOption{
core.NodeInfo(cfg.NodeInfo),
core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
}
for _, addr := range cfg.Listen {
options = append(options, core.ListenAddress(addr))
}
for _, peer := range cfg.Peers {
options = append(options, core.Peer{URI: peer})
}
for intf, peers := range cfg.InterfacePeers {
for _, peer := range peers {
options = append(options, core.Peer{URI: peer, SourceInterface: intf})
}
}
for _, allowed := range cfg.AllowedPublicKeys {
k, err := hex.DecodeString(allowed)
if err != nil {
panic(err)
}
options = append(options, core.AllowedPublicKey(k[:]))
}
if n.core, err = core.New(sk[:], logger, options...); err != nil {
panic(err)
}
}
// Setup the admin socket.
{
options := []admin.SetupOption{
admin.ListenAddress(cfg.AdminListen),
}
if n.admin, err = admin.New(n.core, logger, options...); err != nil {
panic(err)
}
if n.admin != nil {
n.admin.SetupAdminHandlers()
}
}
// Setup the multicast module.
{
options := []multicast.SetupOption{}
for _, intf := range cfg.MulticastInterfaces {
options = append(options, multicast.MulticastInterface{
Regex: regexp.MustCompile(intf.Regex),
Beacon: intf.Beacon,
Listen: intf.Listen,
Port: intf.Port,
Priority: uint8(intf.Priority),
})
}
if n.multicast, err = multicast.New(n.core, logger, options...); err != nil {
panic(err)
}
if n.admin != nil && n.multicast != nil {
n.multicast.SetupAdminHandlers(n.admin)
}
}
// Setup the TUN module.
{
options := []tun.SetupOption{
tun.InterfaceName(cfg.IfName),
tun.InterfaceMTU(cfg.IfMTU),
}
if n.tun, err = tun.New(ipv6rwc.NewReadWriteCloser(n.core), logger, options...); err != nil {
panic(err)
}
if n.admin != nil && n.tun != nil {
n.tun.SetupAdminHandlers(n.admin)
}
}
// Make some nice output that tells us what our IPv6 address and subnet are.
// This is just logged to stdout for the user.
address := n.core.Address()
subnet := n.core.Subnet()
public := n.core.GetSelf().Key
logger.Infof("Your public key is %s", hex.EncodeToString(public[:]))
logger.Infof("Your IPv6 address is %s", address.String())
logger.Infof("Your IPv6 subnet is %s", subnet.String())
// Block until we are told to shut down.
<-ctx.Done()
// Shut down the node.
_ = n.admin.Stop()
_ = n.multicast.Stop()
_ = n.tun.Stop()
n.core.Stop()
}
func main() {
args := getArgs()
// Catch interrupts from the operating system to exit gracefully.
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
// Capture the service being stopped on Windows.
minwinsvc.SetOnExit(cancel)
// Start the node, block and then wait for it to shut down.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
run(args, ctx)
}()
wg.Wait()
}

View file

@ -7,10 +7,10 @@ import (
"log" "log"
"os" "os"
"github.com/hjson/hjson-go" "github.com/hjson/hjson-go/v4"
"golang.org/x/text/encoding/unicode" "golang.org/x/text/encoding/unicode"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/config"
) )
type CmdLineEnv struct { type CmdLineEnv struct {
@ -21,7 +21,7 @@ type CmdLineEnv struct {
func newCmdLineEnv() CmdLineEnv { func newCmdLineEnv() CmdLineEnv {
var cmdLineEnv CmdLineEnv var cmdLineEnv CmdLineEnv
cmdLineEnv.endpoint = defaults.GetDefaults().DefaultAdminListen cmdLineEnv.endpoint = config.GetDefaults().DefaultAdminListen
return cmdLineEnv return cmdLineEnv
} }
@ -38,10 +38,8 @@ func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() {
fmt.Println("Examples:") fmt.Println("Examples:")
fmt.Println(" - ", os.Args[0], "list") fmt.Println(" - ", os.Args[0], "list")
fmt.Println(" - ", os.Args[0], "getPeers") fmt.Println(" - ", os.Args[0], "getPeers")
fmt.Println(" - ", os.Args[0], "-v getSelf") fmt.Println(" - ", os.Args[0], "-endpoint=tcp://localhost:9001 getPeers")
fmt.Println(" - ", os.Args[0], "setTunTap name=auto mtu=1500 tap_mode=false") fmt.Println(" - ", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getPeers")
fmt.Println(" - ", os.Args[0], "-endpoint=tcp://localhost:9001 getDHT")
fmt.Println(" - ", os.Args[0], "-endpoint=unix:///var/run/ygg.sock getDHT")
} }
server := flag.String("endpoint", cmdLineEnv.endpoint, "Admin socket endpoint") server := flag.String("endpoint", cmdLineEnv.endpoint, "Admin socket endpoint")
@ -58,31 +56,31 @@ func (cmdLineEnv *CmdLineEnv) parseFlagsAndArgs() {
func (cmdLineEnv *CmdLineEnv) setEndpoint(logger *log.Logger) { func (cmdLineEnv *CmdLineEnv) setEndpoint(logger *log.Logger) {
if cmdLineEnv.server == cmdLineEnv.endpoint { if cmdLineEnv.server == cmdLineEnv.endpoint {
if config, err := os.ReadFile(defaults.GetDefaults().DefaultConfigFile); err == nil { if cfg, err := os.ReadFile(config.GetDefaults().DefaultConfigFile); err == nil {
if bytes.Equal(config[0:2], []byte{0xFF, 0xFE}) || if bytes.Equal(cfg[0:2], []byte{0xFF, 0xFE}) ||
bytes.Equal(config[0:2], []byte{0xFE, 0xFF}) { bytes.Equal(cfg[0:2], []byte{0xFE, 0xFF}) {
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM) utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
decoder := utf.NewDecoder() decoder := utf.NewDecoder()
config, err = decoder.Bytes(config) cfg, err = decoder.Bytes(cfg)
if err != nil { if err != nil {
panic(err) panic(err)
} }
} }
var dat map[string]interface{} var dat map[string]interface{}
if err := hjson.Unmarshal(config, &dat); err != nil { if err := hjson.Unmarshal(cfg, &dat); err != nil {
panic(err) panic(err)
} }
if ep, ok := dat["AdminListen"].(string); ok && (ep != "none" && ep != "") { if ep, ok := dat["AdminListen"].(string); ok && (ep != "none" && ep != "") {
cmdLineEnv.endpoint = ep cmdLineEnv.endpoint = ep
logger.Println("Found platform default config file", defaults.GetDefaults().DefaultConfigFile) logger.Println("Found platform default config file", config.GetDefaults().DefaultConfigFile)
logger.Println("Using endpoint", cmdLineEnv.endpoint, "from AdminListen") logger.Println("Using endpoint", cmdLineEnv.endpoint, "from AdminListen")
} else { } else {
logger.Println("Configuration file doesn't contain appropriate AdminListen option") logger.Println("Configuration file doesn't contain appropriate AdminListen option")
logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen) logger.Println("Falling back to platform default", config.GetDefaults().DefaultAdminListen)
} }
} else { } else {
logger.Println("Can't open config file from default location", defaults.GetDefaults().DefaultConfigFile) logger.Println("Can't open config file from default location", config.GetDefaults().DefaultConfigFile)
logger.Println("Falling back to platform default", defaults.GetDefaults().DefaultAdminListen) logger.Println("Falling back to platform default", config.GetDefaults().DefaultAdminListen)
} }
} else { } else {
cmdLineEnv.endpoint = cmdLineEnv.server cmdLineEnv.endpoint = cmdLineEnv.server

View file

@ -13,6 +13,8 @@ import (
"strings" "strings"
"time" "time"
"suah.dev/protect"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/yggdrasil-network/yggdrasil-go/src/admin" "github.com/yggdrasil-network/yggdrasil-go/src/admin"
"github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/core"
@ -22,6 +24,11 @@ import (
) )
func main() { func main() {
// read config, speak DNS/TCP and/or over a UNIX socket
if err := protect.Pledge("stdio rpath inet unix dns"); err != nil {
panic(err)
}
// makes sure we can use defer and still return an error code to the OS // makes sure we can use defer and still return an error code to the OS
os.Exit(run()) os.Exit(run())
} }
@ -78,6 +85,11 @@ func run() int {
panic(err) panic(err)
} }
// config and socket are done, work without unprivileges
if err := protect.Pledge("stdio"); err != nil {
panic(err)
}
logger.Println("Connected") logger.Println("Connected")
defer conn.Close() defer conn.Close()
@ -165,7 +177,7 @@ func run() int {
table.Append([]string{"Build version:", resp.BuildVersion}) table.Append([]string{"Build version:", resp.BuildVersion})
table.Append([]string{"IPv6 address:", resp.IPAddress}) table.Append([]string{"IPv6 address:", resp.IPAddress})
table.Append([]string{"IPv6 subnet:", resp.Subnet}) table.Append([]string{"IPv6 subnet:", resp.Subnet})
table.Append([]string{"Coordinates:", fmt.Sprintf("%v", resp.Coords)}) table.Append([]string{"Routing table size:", fmt.Sprintf("%d", resp.RoutingEntries)})
table.Append([]string{"Public key:", resp.PublicKey}) table.Append([]string{"Public key:", resp.PublicKey})
table.Render() table.Render()
@ -174,33 +186,61 @@ func run() int {
if err := json.Unmarshal(recv.Response, &resp); err != nil { if err := json.Unmarshal(recv.Response, &resp); err != nil {
panic(err) panic(err)
} }
table.SetHeader([]string{"Port", "Public Key", "IP Address", "Uptime", "RX", "TX", "Pr", "URI"}) table.SetHeader([]string{"URI", "State", "Dir", "IP Address", "Uptime", "RTT", "RX", "TX", "Down", "Up", "Pr", "Cost", "Last Error"})
for _, peer := range resp.Peers { for _, peer := range resp.Peers {
state, lasterr, dir, rtt, rxr, txr := "Up", "-", "Out", "-", "-", "-"
if !peer.Up {
state, lasterr = "Down", fmt.Sprintf("%s ago: %s", peer.LastErrorTime.Round(time.Second), peer.LastError)
} else if rttms := float64(peer.Latency.Microseconds()) / 1000; rttms > 0 {
rtt = fmt.Sprintf("%.02fms", rttms)
}
if peer.Inbound {
dir = "In"
}
uristring := peer.URI
if uri, err := url.Parse(peer.URI); err == nil {
uri.RawQuery = ""
uristring = uri.String()
}
if peer.RXRate > 0 {
rxr = peer.RXRate.String() + "/s"
}
if peer.TXRate > 0 {
txr = peer.TXRate.String() + "/s"
}
table.Append([]string{ table.Append([]string{
fmt.Sprintf("%d", peer.Port), uristring,
peer.PublicKey, state,
dir,
peer.IPAddress, peer.IPAddress,
(time.Duration(peer.Uptime) * time.Second).String(), (time.Duration(peer.Uptime) * time.Second).String(),
rtt,
peer.RXBytes.String(), peer.RXBytes.String(),
peer.TXBytes.String(), peer.TXBytes.String(),
rxr,
txr,
fmt.Sprintf("%d", peer.Priority), fmt.Sprintf("%d", peer.Priority),
peer.Remote, fmt.Sprintf("%d", peer.Cost),
lasterr,
}) })
} }
table.Render() table.Render()
case "getdht": case "gettree":
var resp admin.GetDHTResponse var resp admin.GetTreeResponse
if err := json.Unmarshal(recv.Response, &resp); err != nil { if err := json.Unmarshal(recv.Response, &resp); err != nil {
panic(err) panic(err)
} }
table.SetHeader([]string{"Public Key", "IP Address", "Port", "Rest"}) //table.SetHeader([]string{"Public Key", "IP Address", "Port", "Rest"})
for _, dht := range resp.DHT { table.SetHeader([]string{"Public Key", "IP Address", "Parent", "Sequence"})
for _, tree := range resp.Tree {
table.Append([]string{ table.Append([]string{
dht.PublicKey, tree.PublicKey,
dht.IPAddress, tree.IPAddress,
fmt.Sprintf("%d", dht.Port), tree.Parent,
fmt.Sprintf("%d", dht.Rest), fmt.Sprintf("%d", tree.Sequence),
//fmt.Sprintf("%d", dht.Port),
//fmt.Sprintf("%d", dht.Rest),
}) })
} }
table.Render() table.Render()
@ -210,12 +250,13 @@ func run() int {
if err := json.Unmarshal(recv.Response, &resp); err != nil { if err := json.Unmarshal(recv.Response, &resp); err != nil {
panic(err) panic(err)
} }
table.SetHeader([]string{"Public Key", "IP Address", "Path"}) table.SetHeader([]string{"Public Key", "IP Address", "Path", "Seq"})
for _, p := range resp.Paths { for _, p := range resp.Paths {
table.Append([]string{ table.Append([]string{
p.PublicKey, p.PublicKey,
p.IPAddress, p.IPAddress,
fmt.Sprintf("%v", p.Path), fmt.Sprintf("%v", p.Path),
fmt.Sprintf("%d", p.Sequence),
}) })
} }
table.Render() table.Render()
@ -252,9 +293,21 @@ func run() int {
if err := json.Unmarshal(recv.Response, &resp); err != nil { if err := json.Unmarshal(recv.Response, &resp); err != nil {
panic(err) panic(err)
} }
table.SetHeader([]string{"Interface"}) fmtBool := func(b bool) string {
if b {
return "Yes"
}
return "-"
}
table.SetHeader([]string{"Name", "Listen Address", "Beacon", "Listen", "Password"})
for _, p := range resp.Interfaces { for _, p := range resp.Interfaces {
table.Append([]string{p}) table.Append([]string{
p.Name,
p.Address,
fmtBool(p.Beacon),
fmtBool(p.Listen),
fmtBool(p.Password),
})
} }
table.Render() table.Render()

BIN
contrib/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -1,7 +1,5 @@
/* /*
This file generates crypto keys for [ansible-yggdrasil](https://github.com/jcgruenhage/ansible-yggdrasil/) This file generates crypto keys for [ansible-yggdrasil](https://github.com/jcgruenhage/ansible-yggdrasil/)
*/ */
package main package main

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

View file

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

View file

@ -1,9 +1,9 @@
package mobile package mobile
import ( import (
"crypto/ed25519"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt"
"net" "net"
"regexp" "regexp"
@ -12,12 +12,10 @@ import (
"github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config" "github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/core"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc" "github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc"
"github.com/yggdrasil-network/yggdrasil-go/src/multicast" "github.com/yggdrasil-network/yggdrasil-go/src/multicast"
"github.com/yggdrasil-network/yggdrasil-go/src/tun"
"github.com/yggdrasil-network/yggdrasil-go/src/version" "github.com/yggdrasil-network/yggdrasil-go/src/version"
_ "golang.org/x/mobile/bind"
) )
// Yggdrasil mobile package is meant to "plug the gap" for mobile support, as // Yggdrasil mobile package is meant to "plug the gap" for mobile support, as
@ -30,7 +28,9 @@ type Yggdrasil struct {
iprwc *ipv6rwc.ReadWriteCloser iprwc *ipv6rwc.ReadWriteCloser
config *config.NodeConfig config *config.NodeConfig
multicast *multicast.Multicast multicast *multicast.Multicast
tun *tun.TunAdapter // optional
log MobileLogger log MobileLogger
logger *log.Logger
} }
// StartAutoconfigure starts a node with a randomly generated config // StartAutoconfigure starts a node with a randomly generated config
@ -41,21 +41,28 @@ func (m *Yggdrasil) StartAutoconfigure() error {
// StartJSON starts a node with the given JSON config. You can get JSON config // StartJSON starts a node with the given JSON config. You can get JSON config
// (rather than HJSON) by using the GenerateConfigJSON() function // (rather than HJSON) by using the GenerateConfigJSON() function
func (m *Yggdrasil) StartJSON(configjson []byte) error { func (m *Yggdrasil) StartJSON(configjson []byte) error {
setMemLimitIfPossible()
logger := log.New(m.log, "", 0) logger := log.New(m.log, "", 0)
logger.EnableLevel("error") logger.EnableLevel("error")
logger.EnableLevel("warn") logger.EnableLevel("warn")
logger.EnableLevel("info") logger.EnableLevel("info")
m.config = defaults.GenerateConfig() m.logger = logger
if err := json.Unmarshal(configjson, &m.config); err != nil { m.config = config.GenerateConfig()
if err := m.config.UnmarshalHJSON(configjson); err != nil {
return err return err
} }
// Setup the Yggdrasil node itself. // Set up the Yggdrasil node itself.
{ {
sk, err := hex.DecodeString(m.config.PrivateKey) iprange := net.IPNet{
if err != nil { IP: net.ParseIP("200::"),
panic(err) Mask: net.CIDRMask(7, 128),
}
options := []core.SetupOption{
core.PeerFilter(func(ip net.IP) bool {
return !iprange.Contains(ip)
}),
} }
options := []core.SetupOption{}
for _, peer := range m.config.Peers { for _, peer := range m.config.Peers {
options = append(options, core.Peer{URI: peer}) options = append(options, core.Peer{URI: peer})
} }
@ -71,15 +78,24 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
} }
options = append(options, core.AllowedPublicKey(k[:])) options = append(options, core.AllowedPublicKey(k[:]))
} }
m.core, err = core.New(sk[:], logger, options...) for _, lAddr := range m.config.Listen {
options = append(options, core.ListenAddress(lAddr))
}
var err error
m.core, err = core.New(m.config.Certificate, logger, options...)
if err != nil { if err != nil {
panic(err) panic(err)
} }
address, subnet := m.core.Address(), m.core.Subnet()
logger.Infof("Your public key is %s", hex.EncodeToString(m.core.PublicKey()))
logger.Infof("Your IPv6 address is %s", address.String())
logger.Infof("Your IPv6 subnet is %s", subnet.String())
} }
// Setup the multicast module. // Set up the multicast module.
if len(m.config.MulticastInterfaces) > 0 { if len(m.config.MulticastInterfaces) > 0 {
var err error var err error
logger.Infof("Initializing multicast %s", "")
options := []multicast.SetupOption{} options := []multicast.SetupOption{}
for _, intf := range m.config.MulticastInterfaces { for _, intf := range m.config.MulticastInterfaces {
options = append(options, multicast.MulticastInterface{ options = append(options, multicast.MulticastInterface{
@ -88,9 +104,11 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
Listen: intf.Listen, Listen: intf.Listen,
Port: intf.Port, Port: intf.Port,
Priority: uint8(intf.Priority), Priority: uint8(intf.Priority),
Password: intf.Password,
}) })
} }
m.multicast, err = multicast.New(m.core, logger, options...) logger.Infof("Starting multicast %s", "")
m.multicast, err = multicast.New(m.core, m.logger, options...)
if err != nil { if err != nil {
logger.Errorln("An error occurred starting multicast:", err) logger.Errorln("An error occurred starting multicast:", err)
} }
@ -151,10 +169,20 @@ func (m *Yggdrasil) RecvBuffer(buf []byte) (int, error) {
func (m *Yggdrasil) Stop() error { func (m *Yggdrasil) Stop() error {
logger := log.New(m.log, "", 0) logger := log.New(m.log, "", 0)
logger.EnableLevel("info") logger.EnableLevel("info")
logger.Infof("Stop the mobile Yggdrasil instance %s", "") logger.Infof("Stopping the mobile Yggdrasil instance %s", "")
if err := m.multicast.Stop(); err != nil { if m.multicast != nil {
return err logger.Infof("Stopping multicast %s", "")
if err := m.multicast.Stop(); err != nil {
return err
}
} }
logger.Infof("Stopping TUN device %s", "")
if m.tun != nil {
if err := m.tun.Stop(); err != nil {
return err
}
}
logger.Infof("Stopping Yggdrasil core %s", "")
m.core.Stop() m.core.Stop()
return nil return nil
} }
@ -166,7 +194,7 @@ func (m *Yggdrasil) RetryPeersNow() {
// GenerateConfigJSON generates mobile-friendly configuration in JSON format // GenerateConfigJSON generates mobile-friendly configuration in JSON format
func GenerateConfigJSON() []byte { func GenerateConfigJSON() []byte {
nc := defaults.GenerateConfig() nc := config.GenerateConfig()
nc.IfName = "none" nc.IfName = "none"
if json, err := json.Marshal(nc); err == nil { if json, err := json.Marshal(nc); err == nil {
return json return json
@ -191,9 +219,9 @@ func (m *Yggdrasil) GetPublicKeyString() string {
return hex.EncodeToString(m.core.GetSelf().Key) return hex.EncodeToString(m.core.GetSelf().Key)
} }
// GetCoordsString gets the node's coordinates // GetRoutingEntries gets the number of entries in the routing table
func (m *Yggdrasil) GetCoordsString() string { func (m *Yggdrasil) GetRoutingEntries() int {
return fmt.Sprintf("%v", m.core.GetSelf().Coords) return int(m.core.GetSelf().RoutingEntries)
} }
func (m *Yggdrasil) GetPeersJSON() (result string) { func (m *Yggdrasil) GetPeersJSON() (result string) {
@ -202,8 +230,11 @@ func (m *Yggdrasil) GetPeersJSON() (result string) {
IP string IP string
}{} }{}
for _, v := range m.core.GetPeers() { for _, v := range m.core.GetPeers() {
a := address.AddrForKey(v.Key) var ip string
ip := net.IP(a[:]).String() if v.Key != nil {
a := address.AddrForKey(v.Key)
ip = net.IP(a[:]).String()
}
peers = append(peers, struct { peers = append(peers, struct {
core.PeerInfo core.PeerInfo
IP string IP string
@ -219,8 +250,16 @@ func (m *Yggdrasil) GetPeersJSON() (result string) {
} }
} }
func (m *Yggdrasil) GetDHTJSON() (result string) { func (m *Yggdrasil) GetPathsJSON() (result string) {
if res, err := json.Marshal(m.core.GetDHT()); err == nil { if res, err := json.Marshal(m.core.GetPaths()); err == nil {
return string(res)
} else {
return "{}"
}
}
func (m *Yggdrasil) GetTreeJSON() (result string) {
if res, err := json.Marshal(m.core.GetTree()); err == nil {
return string(res) return string(res)
} else { } else {
return "{}" return "{}"
@ -235,3 +274,28 @@ func (m *Yggdrasil) GetMTU() int {
func GetVersion() string { func GetVersion() string {
return version.BuildVersion() return version.BuildVersion()
} }
type ConfigSummary struct {
PublicKey string
IPv6Address string
IPv6Subnet string
}
func SummaryForConfig(b []byte) *ConfigSummary {
cfg := config.GenerateConfig()
if err := cfg.UnmarshalHJSON(b); err != nil {
return nil
}
pub := ed25519.PrivateKey(cfg.PrivateKey).Public().(ed25519.PublicKey)
hpub := hex.EncodeToString(pub)
addr := net.IP(address.AddrForKey(pub)[:])
snet := net.IPNet{
IP: append(address.SubnetForKey(pub)[:], 0, 0, 0, 0, 0, 0, 0, 0),
Mask: net.CIDRMask(64, 128),
}
return &ConfigSummary{
PublicKey: hpub,
IPv6Address: addr.String(),
IPv6Subnet: snet.String(),
}
}

View file

@ -1,5 +1,5 @@
//go:build ios //go:build ios || darwin
// +build ios // +build ios darwin
package mobile package mobile
@ -15,6 +15,8 @@ void Log(const char *text) {
import "C" import "C"
import ( import (
"unsafe" "unsafe"
"github.com/yggdrasil-network/yggdrasil-go/src/tun"
) )
type MobileLogger struct { type MobileLogger struct {
@ -26,3 +28,13 @@ func (nsl MobileLogger) Write(p []byte) (n int, err error) {
C.Log(cstr) C.Log(cstr)
return len(p), nil return len(p), nil
} }
func (m *Yggdrasil) TakeOverTUN(fd int32) error {
options := []tun.SetupOption{
tun.FileDescriptor(fd),
tun.InterfaceMTU(m.iprwc.MTU()),
}
var err error
m.tun, err = tun.New(m.iprwc, m.logger, options...)
return err
}

View file

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

View file

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

View file

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

View file

@ -1,15 +1,27 @@
package mobile package mobile
import "testing" import (
"os"
"testing"
"github.com/gologme/log"
)
func TestStartYggdrasil(t *testing.T) { func TestStartYggdrasil(t *testing.T) {
ygg := &Yggdrasil{} logger := log.New(os.Stdout, "", 0)
logger.EnableLevel("error")
logger.EnableLevel("warn")
logger.EnableLevel("info")
ygg := &Yggdrasil{
logger: logger,
}
if err := ygg.StartAutoconfigure(); err != nil { if err := ygg.StartAutoconfigure(); err != nil {
t.Fatalf("Failed to start Yggdrasil: %s", err) t.Fatalf("Failed to start Yggdrasil: %s", err)
} }
t.Log("Address:", ygg.GetAddressString()) t.Log("Address:", ygg.GetAddressString())
t.Log("Subnet:", ygg.GetSubnetString()) t.Log("Subnet:", ygg.GetSubnetString())
t.Log("Coords:", ygg.GetCoordsString()) t.Log("Routing entries:", ygg.GetRoutingEntries())
if err := ygg.Stop(); err != nil { if err := ygg.Stop(); err != nil {
t.Fatalf("Failed to stop Yggdrasil: %s", err) t.Fatalf("Failed to stop Yggdrasil: %s", err)
} }

View file

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

View file

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

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

55
go.mod
View file

@ -1,38 +1,49 @@
module github.com/yggdrasil-network/yggdrasil-go module github.com/yggdrasil-network/yggdrasil-go
go 1.17 go 1.22
require ( require (
github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439 github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d
github.com/cheggaaa/pb/v3 v3.0.8 github.com/cheggaaa/pb/v3 v3.1.5
github.com/gologme/log v1.2.0 github.com/coder/websocket v1.8.12
github.com/gologme/log v1.3.0
github.com/hashicorp/go-syslog v1.0.0 github.com/hashicorp/go-syslog v1.0.0
github.com/hjson/hjson-go v3.1.0+incompatible github.com/hjson/hjson-go/v4 v4.4.0
github.com/kardianos/minwinsvc v1.0.2 github.com/kardianos/minwinsvc v1.0.2
github.com/mitchellh/mapstructure v1.4.1 github.com/quic-go/quic-go v0.48.2
github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netlink v1.3.0
golang.org/x/mobile v0.0.0-20221110043201-43a038452099 github.com/wlynxg/anet v0.0.5
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b golang.org/x/crypto v0.33.0
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 golang.org/x/net v0.35.0
golang.org/x/text v0.3.8 golang.org/x/sys v0.30.0
golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a golang.org/x/text v0.22.0
golang.zx2c4.com/wireguard/windows v0.4.12 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
golang.zx2c4.com/wireguard/windows v0.5.3
) )
require ( require (
github.com/mattn/go-colorable v0.1.8 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/bits-and-blooms/bloom/v3 v3.7.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect go.uber.org/mock v0.5.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/tools v0.1.12 // indirect golang.org/x/mod v0.19.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/tools v0.23.0 // indirect
) )
require ( require (
github.com/VividCortex/ewma v1.2.0 // indirect github.com/VividCortex/ewma v1.2.0 // indirect
github.com/fatih/color v1.12.0 // indirect github.com/fatih/color v1.18.0 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/olekukonko/tablewriter v0.0.5 github.com/olekukonko/tablewriter v0.0.5
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect github.com/vishvananda/netns v0.0.5 // indirect
suah.dev/protect v1.2.4
) )

217
go.sum
View file

@ -1,120 +1,119 @@
github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439 h1:eOW6/XIs06TnUn9GPCnfv71CQZw8edP3u3mH3lZt6iM= github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3 h1:d8N0z+udAnbU5PdjpLSNPTWlqeU/nnYsQ42B6+879aw=
github.com/Arceliar/ironwood v0.0.0-20221115123222-ec61cea2f439/go.mod h1:RP72rucOFm5udrnEzTmIWLRVGQiV/fSUAQXJ0RST/nk= github.com/Arceliar/ironwood v0.0.0-20241213013129-743fe2fccbd3/go.mod h1:SrrElc3FFMpYCODSr11jWbLFeOM8WsY+DbDY/l2AXF0=
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979 h1:WndgpSW13S32VLQ3ugUxx2EnnWmgba1kCqPkd4Gk1yQ= github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d h1:UK9fsWbWqwIQkMCz1CP+v5pGbsGoWAw6g4AyvMpm1EM=
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979/go.mod h1:6Lkn+/zJilRMsKmbmG1RPoamiArC6HS73xbwRyp3UyI= github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d/go.mod h1:BCnxhRf47C/dy/e/D2pmB8NkB3dQVIrkD98b220rx5Q=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/cheggaaa/pb/v3 v3.0.8 h1:bC8oemdChbke2FHIIGy9mn4DPJ2caZYQnfbRqwmdCoA= github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA= github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= github.com/bits-and-blooms/bloom/v3 v3.7.0 h1:VfknkqV4xI+PsaDIsoHueyxVDZrfvMn56jeWUzvzdls=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/bits-and-blooms/bloom/v3 v3.7.0/go.mod h1:VKlUSvp0lFIYqxJjzdnSsZEw4iHb1kOL2tfHTgyJBHg=
github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c= github.com/cheggaaa/pb/v3 v3.1.5 h1:QuuUzeM2WsAqG2gMqtzaWithDJv0i+i6UlnwSCI4QLk=
github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/cheggaaa/pb/v3 v3.1.5/go.mod h1:CrxkeghYTXi1lQBEI7jSn+3svI3cuc19haAj6jM60XI=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/gologme/log v1.3.0 h1:l781G4dE+pbigClDSDzSaaYKtiueHCILUa/qSDsmHAo=
github.com/gologme/log v1.3.0/go.mod h1:yKT+DvIPdDdDoPtqFrFxheooyVmoqi0BAsw+erN3wA4=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hjson/hjson-go v3.1.0+incompatible h1:DY/9yE8ey8Zv22bY+mHV1uk2yRy0h8tKhZ77hEdi0Aw= github.com/hjson/hjson-go/v4 v4.4.0 h1:D/NPvqOCH6/eisTb5/ztuIS8GUvmpHaLOcNk1Bjr298=
github.com/hjson/hjson-go v3.1.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio= github.com/hjson/hjson-go/v4 v4.4.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/kardianos/minwinsvc v1.0.2 h1:JmZKFJQrmTGa/WiW+vkJXKmfzdjabuEW4Tirj5lLdR0= github.com/kardianos/minwinsvc v1.0.2 h1:JmZKFJQrmTGa/WiW+vkJXKmfzdjabuEW4Tirj5lLdR0=
github.com/kardianos/minwinsvc v1.0.2/go.mod h1:LUZNYhNmxujx2tR7FbdxqYJ9XDDoCd3MQcl1o//FWl4= github.com/kardianos/minwinsvc v1.0.2/go.mod h1:LUZNYhNmxujx2tR7FbdxqYJ9XDDoCd3MQcl1o//FWl4=
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/mobile v0.0.0-20221110043201-43a038452099 h1:aIu0lKmfdgtn2uTj7JI2oN4TUrQvgB+wzTPO23bCKt8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mobile v0.0.0-20221110043201-43a038452099/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/net v0.0.0-20210927181540-4e4d966f7476/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= suah.dev/protect v1.2.4 h1:iVZG/zQB63FKNpITDYM/cXoAeCTIjCiXHuFVByJFDzg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= suah.dev/protect v1.2.4/go.mod h1:vVrquYO3u1Ep9Ez2z8x+6N6/czm+TBmWKZfiXU2tb54=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4=
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard v0.0.0-20211012062646-82d2aa87aa62/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU=
golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a h1:tTbyylK9/D3u/wEP26Vx7L700UpY48nhioJWZM1vhZw=
golang.zx2c4.com/wireguard v0.0.0-20211017052713-f87e87af0d9a/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU=
golang.zx2c4.com/wireguard/windows v0.4.12 h1:CUmbdWKVNzTSsVb4yUAiEwL3KsabdJkEPdDjCHxBlhA=
golang.zx2c4.com/wireguard/windows v0.4.12/go.mod h1:PW4y+d9oY83XU9rRwRwrJDwEMuhVjMxu2gfD1cfzS7w=

View file

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

View file

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

View file

@ -1,5 +1,10 @@
package admin package admin
import (
"fmt"
"net/url"
)
type AddPeerRequest struct { type AddPeerRequest struct {
Uri string `json:"uri"` Uri string `json:"uri"`
Sintf string `json:"interface,omitempty"` Sintf string `json:"interface,omitempty"`
@ -7,6 +12,10 @@ type AddPeerRequest struct {
type AddPeerResponse struct{} type AddPeerResponse struct{}
func (a *AdminSocket) addPeerHandler(req *AddPeerRequest, res *AddPeerResponse) error { func (a *AdminSocket) addPeerHandler(req *AddPeerRequest, _ *AddPeerResponse) error {
return a.core.AddPeer(req.Uri, req.Sintf) u, err := url.Parse(req.Uri)
if err != nil {
return fmt.Errorf("unable to parse peering URI: %w", err)
}
return a.core.AddPeer(u, req.Sintf)
} }

View file

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

View file

@ -1,41 +0,0 @@
package admin
import (
"encoding/hex"
"net"
"sort"
"strings"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
type GetDHTRequest struct{}
type GetDHTResponse struct {
DHT []DHTEntry `json:"dht"`
}
type DHTEntry struct {
IPAddress string `json:"address"`
PublicKey string `json:"key"`
Port uint64 `json:"port"`
Rest uint64 `json:"rest"`
}
func (a *AdminSocket) getDHTHandler(req *GetDHTRequest, res *GetDHTResponse) error {
dht := a.core.GetDHT()
res.DHT = make([]DHTEntry, 0, len(dht))
for _, d := range dht {
addr := address.AddrForKey(d.Key)
res.DHT = append(res.DHT, DHTEntry{
IPAddress: net.IP(addr[:]).String(),
PublicKey: hex.EncodeToString(d.Key[:]),
Port: d.Port,
Rest: d.Rest,
})
}
sort.SliceStable(res.DHT, func(i, j int) bool {
return strings.Compare(res.DHT[i].PublicKey, res.DHT[j].PublicKey) < 0
})
return nil
}

View file

@ -3,7 +3,7 @@ package admin
import ( import (
"encoding/hex" "encoding/hex"
"net" "net"
"sort" "slices"
"strings" "strings"
"github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/address"
@ -20,9 +20,10 @@ type PathEntry struct {
IPAddress string `json:"address"` IPAddress string `json:"address"`
PublicKey string `json:"key"` PublicKey string `json:"key"`
Path []uint64 `json:"path"` Path []uint64 `json:"path"`
Sequence uint64 `json:"sequence"`
} }
func (a *AdminSocket) getPathsHandler(req *GetPathsRequest, res *GetPathsResponse) error { func (a *AdminSocket) getPathsHandler(_ *GetPathsRequest, res *GetPathsResponse) error {
paths := a.core.GetPaths() paths := a.core.GetPaths()
res.Paths = make([]PathEntry, 0, len(paths)) res.Paths = make([]PathEntry, 0, len(paths))
for _, p := range paths { for _, p := range paths {
@ -31,10 +32,11 @@ func (a *AdminSocket) getPathsHandler(req *GetPathsRequest, res *GetPathsRespons
IPAddress: net.IP(addr[:]).String(), IPAddress: net.IP(addr[:]).String(),
PublicKey: hex.EncodeToString(p.Key), PublicKey: hex.EncodeToString(p.Key),
Path: p.Path, Path: p.Path,
Sequence: p.Sequence,
}) })
} }
sort.SliceStable(res.Paths, func(i, j int) bool { slices.SortStableFunc(res.Paths, func(a, b PathEntry) int {
return strings.Compare(res.Paths[i].PublicKey, res.Paths[j].PublicKey) < 0 return strings.Compare(a.PublicKey, b.PublicKey)
}) })
return nil return nil
} }

View file

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

View file

@ -9,15 +9,15 @@ import (
type GetSelfRequest struct{} type GetSelfRequest struct{}
type GetSelfResponse struct { type GetSelfResponse struct {
BuildName string `json:"build_name"` BuildName string `json:"build_name"`
BuildVersion string `json:"build_version"` BuildVersion string `json:"build_version"`
PublicKey string `json:"key"` PublicKey string `json:"key"`
IPAddress string `json:"address"` IPAddress string `json:"address"`
Coords []uint64 `json:"coords"` RoutingEntries uint64 `json:"routing_entries"`
Subnet string `json:"subnet"` Subnet string `json:"subnet"`
} }
func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse) error { func (a *AdminSocket) getSelfHandler(_ *GetSelfRequest, res *GetSelfResponse) error {
self := a.core.GetSelf() self := a.core.GetSelf()
snet := a.core.Subnet() snet := a.core.Subnet()
res.BuildName = version.BuildName() res.BuildName = version.BuildName()
@ -25,6 +25,6 @@ func (a *AdminSocket) getSelfHandler(req *GetSelfRequest, res *GetSelfResponse)
res.PublicKey = hex.EncodeToString(self.Key[:]) res.PublicKey = hex.EncodeToString(self.Key[:])
res.IPAddress = a.core.Address().String() res.IPAddress = a.core.Address().String()
res.Subnet = snet.String() res.Subnet = snet.String()
res.Coords = self.Coords res.RoutingEntries = self.RoutingEntries
return nil return nil
} }

View file

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

41
src/admin/gettree.go Normal file
View file

@ -0,0 +1,41 @@
package admin
import (
"encoding/hex"
"net"
"slices"
"strings"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
type GetTreeRequest struct{}
type GetTreeResponse struct {
Tree []TreeEntry `json:"tree"`
}
type TreeEntry struct {
IPAddress string `json:"address"`
PublicKey string `json:"key"`
Parent string `json:"parent"`
Sequence uint64 `json:"sequence"`
}
func (a *AdminSocket) getTreeHandler(_ *GetTreeRequest, res *GetTreeResponse) error {
tree := a.core.GetTree()
res.Tree = make([]TreeEntry, 0, len(tree))
for _, d := range tree {
addr := address.AddrForKey(d.Key)
res.Tree = append(res.Tree, TreeEntry{
IPAddress: net.IP(addr[:]).String(),
PublicKey: hex.EncodeToString(d.Key[:]),
Parent: hex.EncodeToString(d.Parent[:]),
Sequence: d.Sequence,
})
}
slices.SortStableFunc(res.Tree, func(a, b TreeEntry) int {
return strings.Compare(a.PublicKey, b.PublicKey)
})
return nil
}

View file

@ -1,9 +1,24 @@
package admin package admin
import (
"crypto/ed25519"
"encoding/hex"
"encoding/json"
"net"
"sync"
"time"
"github.com/Arceliar/ironwood/network"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
)
func (c *AdminSocket) _applyOption(opt SetupOption) { func (c *AdminSocket) _applyOption(opt SetupOption) {
switch v := opt.(type) { switch v := opt.(type) {
case ListenAddress: case ListenAddress:
c.config.listenaddr = v c.config.listenaddr = v
case LogLookups:
c.logLookups()
} }
} }
@ -14,3 +29,51 @@ type SetupOption interface {
type ListenAddress string type ListenAddress string
func (a ListenAddress) isSetupOption() {} func (a ListenAddress) isSetupOption() {}
type LogLookups struct{}
func (l LogLookups) isSetupOption() {}
func (a *AdminSocket) logLookups() {
type resi struct {
Address string `json:"addr"`
Key string `json:"key"`
Path []uint64 `json:"path"`
Time int64 `json:"time"`
}
type res struct {
Infos []resi `json:"infos"`
}
type info struct {
path []uint64
time time.Time
}
type edk [ed25519.PublicKeySize]byte
infos := make(map[edk]info)
var m sync.Mutex
a.core.PacketConn.PacketConn.Debug.SetDebugLookupLogger(func(l network.DebugLookupInfo) {
var k edk
copy(k[:], l.Key[:])
m.Lock()
infos[k] = info{path: l.Path, time: time.Now()}
m.Unlock()
})
_ = a.AddHandler(
"lookups", "Dump a record of lookups received in the past hour", []string{},
func(in json.RawMessage) (interface{}, error) {
m.Lock()
rs := make([]resi, 0, len(infos))
for k, v := range infos {
if time.Since(v.time) > 24*time.Hour {
// TODO? automatic cleanup, so we don't need to call lookups periodically to prevent leaks
delete(infos, k)
}
a := address.AddrForKey(ed25519.PublicKey(k[:]))
addr := net.IP(a[:]).String()
rs = append(rs, resi{Address: addr, Key: hex.EncodeToString(k[:]), Path: v.path, Time: v.time.Unix()})
}
m.Unlock()
return &res{Infos: rs}, nil
},
)
}

View file

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

View file

@ -17,44 +17,244 @@ configuration option that is not provided.
package config package config
import ( import (
"bytes"
"crypto/ed25519" "crypto/ed25519"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex" "encoding/hex"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"math/big"
"os"
"time"
"github.com/hjson/hjson-go/v4"
"golang.org/x/text/encoding/unicode"
) )
// NodeConfig is the main configuration structure, containing configuration // NodeConfig is the main configuration structure, containing configuration
// options that are necessary for an Yggdrasil node to run. You will need to // options that are necessary for an Yggdrasil node to run. You will need to
// supply one of these structs to the Yggdrasil core when starting a node. // supply one of these structs to the Yggdrasil core when starting a node.
type NodeConfig struct { type NodeConfig struct {
Peers []string `comment:"List of connection strings for outbound peer connections in URI format,\ne.g. tls://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j. These connections\nwill obey the operating system routing table, therefore you should\nuse this section when you may connect via different interfaces."` PrivateKey KeyBytes `json:",omitempty" comment:"Your private key. DO NOT share this with anyone!"`
InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ \"tls://a.b.c.d:e\" ] }.\nNote that SOCKS peerings will NOT be affected by this option and should\ngo in the \"Peers\" section instead."` PrivateKeyPath string `json:",omitempty" comment:"The path to your private key file in PEM format."`
Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."` Certificate *tls.Certificate `json:"-"`
AdminListen string `comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."` Peers []string `comment:"List of outbound peer connection strings (e.g. tls://a.b.c.d:e or\nsocks://a.b.c.d:e/f.g.h.i:j). Connection strings can contain options,\nsee https://yggdrasil-network.github.io/configurationref.html#peers.\nYggdrasil has no concept of bootstrap nodes - all network traffic\nwill transit peer connections. Therefore make sure to only peer with\nnearby nodes that have good connectivity and low latency. Avoid adding\npeers to this list from distant countries as this will worsen your\nnode's connectivity and performance considerably."`
MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Each entry in the list should be a json object which may\ncontain Regex, Beacon, Listen, and Port. Regex is a regular expression\nwhich is matched against an interface name, and interfaces use the\nfirst configuration that they match gainst. Beacon configures whether\nor not the node should send link-local multicast beacons to advertise\ntheir presence, while listening for incoming connections on Port.\nListen controls whether or not the node listens for multicast beacons\nand opens outgoing connections."` InterfacePeers map[string][]string `comment:"List of connection strings for outbound peer connections in URI format,\narranged by source interface, e.g. { \"eth0\": [ \"tls://a.b.c.d:e\" ] }.\nYou should only use this option if your machine is multi-homed and you\nwant to establish outbound peer connections on different interfaces.\nOtherwise you should use \"Peers\"."`
AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast."` Listen []string `comment:"Listen addresses for incoming connections. You will need to add\nlisteners in order to accept incoming peerings from non-local nodes.\nThis is not required if you wish to establish outbound peerings only.\nMulticast peer discovery will work regardless of any listeners set\nhere. Each listener should be specified in URI format as above, e.g.\ntls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces."`
PublicKey string `comment:"Your public key. Your peers may ask you for this to put\ninto their AllowedPublicKeys configuration."` AdminListen string `json:",omitempty" comment:"Listen address for admin connections. Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X. To disable\nthe admin socket, use the value \"none\" instead."`
PrivateKey string `comment:"Your private key. DO NOT share this with anyone!"` MulticastInterfaces []MulticastInterfaceConfig `comment:"Configuration for which interfaces multicast peer discovery should be\nenabled on. Regex is a regular expression which is matched against an\ninterface name, and interfaces use the first configuration that they\nmatch against. Beacon controls whether or not your node advertises its\npresence to others, whereas Listen controls whether or not your node\nlistens out for and tries to connect to other advertising nodes. See\nhttps://yggdrasil-network.github.io/configurationref.html#multicastinterfaces\nfor more supported options."`
AllowedPublicKeys []string `comment:"List of peer public keys to allow incoming peering connections\nfrom. If left empty/undefined then all connections will be allowed\nby default. This does not affect outgoing peerings, nor does it\naffect link-local peers discovered via multicast.\nWARNING: THIS IS NOT A FIREWALL and DOES NOT limit who can reach\nopen ports or services running on your machine!"`
IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."` IfName string `comment:"Local network interface name for TUN adapter, or \"auto\" to select\nan interface automatically, or \"none\" to run without TUN."`
IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` IfMTU uint64 `comment:"Maximum Transmission Unit (MTU) size for your local TUN interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."`
LogLookups bool `json:",omitempty"`
NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."` NodeInfoPrivacy bool `comment:"By default, nodeinfo contains some defaults including the platform,\narchitecture and Yggdrasil version. These can help when surveying\nthe network and diagnosing network routing problems. Enabling\nnodeinfo privacy prevents this, so that only items specified in\n\"NodeInfo\" are sent back if specified."`
NodeInfo map[string]interface{} `comment:"Optional node info. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."` NodeInfo map[string]interface{} `comment:"Optional nodeinfo. This must be a { \"key\": \"value\", ... } map\nor set as null. This is entirely optional but, if set, is visible\nto the whole network on request."`
} }
type MulticastInterfaceConfig struct { type MulticastInterfaceConfig struct {
Regex string Regex string
Beacon bool Beacon bool
Listen bool Listen bool
Port uint16 Port uint16 `json:",omitempty"`
Priority uint64 // really uint8, but gobind won't export it Priority uint64 `json:",omitempty"` // really uint8, but gobind won't export it
Password string
} }
// NewSigningKeys replaces the signing keypair in the NodeConfig with a new // Generates default configuration and returns a pointer to the resulting
// signing keypair. The signing keys are used by the switch to derive the // NodeConfig. This is used when outputting the -genconf parameter and also when
// structure of the spanning tree. // using -autoconf.
func (cfg *NodeConfig) NewKeys() { func GenerateConfig() *NodeConfig {
spub, spriv, err := ed25519.GenerateKey(nil) // Get the defaults for the platform.
defaults := GetDefaults()
// Create a node configuration and populate it.
cfg := new(NodeConfig)
cfg.NewPrivateKey()
cfg.Listen = []string{}
cfg.AdminListen = defaults.DefaultAdminListen
cfg.Peers = []string{}
cfg.InterfacePeers = map[string][]string{}
cfg.AllowedPublicKeys = []string{}
cfg.MulticastInterfaces = defaults.DefaultMulticastInterfaces
cfg.IfName = defaults.DefaultIfName
cfg.IfMTU = defaults.DefaultIfMTU
cfg.NodeInfoPrivacy = false
if err := cfg.postprocessConfig(); err != nil {
panic(err)
}
return cfg
}
func (cfg *NodeConfig) ReadFrom(r io.Reader) (int64, error) {
conf, err := io.ReadAll(r)
if err != nil {
return 0, err
}
n := int64(len(conf))
// If there's a byte order mark - which Windows 10 is now incredibly fond of
// throwing everywhere when it's converting things into UTF-16 for the hell
// of it - remove it and decode back down into UTF-8. This is necessary
// because hjson doesn't know what to do with UTF-16 and will panic
if bytes.Equal(conf[0:2], []byte{0xFF, 0xFE}) ||
bytes.Equal(conf[0:2], []byte{0xFE, 0xFF}) {
utf := unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
decoder := utf.NewDecoder()
conf, err = decoder.Bytes(conf)
if err != nil {
return n, err
}
}
// Generate a new configuration - this gives us a set of sane defaults -
// then parse the configuration we loaded above on top of it. The effect
// of this is that any configuration item that is missing from the provided
// configuration will use a sane default.
*cfg = *GenerateConfig()
if err := cfg.UnmarshalHJSON(conf); err != nil {
return n, err
}
return n, nil
}
func (cfg *NodeConfig) UnmarshalHJSON(b []byte) error {
if err := hjson.Unmarshal(b, cfg); err != nil {
return err
}
return cfg.postprocessConfig()
}
func (cfg *NodeConfig) postprocessConfig() error {
if cfg.PrivateKeyPath != "" {
cfg.PrivateKey = nil
f, err := os.ReadFile(cfg.PrivateKeyPath)
if err != nil {
return err
}
if err := cfg.UnmarshalPEMPrivateKey(f); err != nil {
return err
}
}
switch {
case cfg.Certificate == nil:
// No self-signed certificate has been generated yet.
fallthrough
case !bytes.Equal(cfg.Certificate.PrivateKey.(ed25519.PrivateKey), cfg.PrivateKey):
// A self-signed certificate was generated but the private
// key has changed since then, possibly because a new config
// was parsed.
if err := cfg.GenerateSelfSignedCertificate(); err != nil {
return err
}
}
return nil
}
// RFC5280 section 4.1.2.5
var notAfterNeverExpires = time.Date(9999, time.December, 31, 23, 59, 59, 0, time.UTC)
func (cfg *NodeConfig) GenerateSelfSignedCertificate() error {
key, err := cfg.MarshalPEMPrivateKey()
if err != nil {
return err
}
cert, err := cfg.MarshalPEMCertificate()
if err != nil {
return err
}
tlsCert, err := tls.X509KeyPair(cert, key)
if err != nil {
return err
}
cfg.Certificate = &tlsCert
return nil
}
func (cfg *NodeConfig) MarshalPEMCertificate() ([]byte, error) {
privateKey := ed25519.PrivateKey(cfg.PrivateKey)
publicKey := privateKey.Public().(ed25519.PublicKey)
cert := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: hex.EncodeToString(publicKey),
},
NotBefore: time.Now(),
NotAfter: notAfterNeverExpires,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
certbytes, err := x509.CreateCertificate(rand.Reader, cert, cert, publicKey, privateKey)
if err != nil {
return nil, err
}
block := &pem.Block{
Type: "CERTIFICATE",
Bytes: certbytes,
}
return pem.EncodeToMemory(block), nil
}
func (cfg *NodeConfig) NewPrivateKey() {
_, spriv, err := ed25519.GenerateKey(nil)
if err != nil { if err != nil {
panic(err) panic(err)
} }
cfg.PublicKey = hex.EncodeToString(spub[:]) cfg.PrivateKey = KeyBytes(spriv)
cfg.PrivateKey = hex.EncodeToString(spriv[:]) }
func (cfg *NodeConfig) MarshalPEMPrivateKey() ([]byte, error) {
b, err := x509.MarshalPKCS8PrivateKey(ed25519.PrivateKey(cfg.PrivateKey))
if err != nil {
return nil, fmt.Errorf("failed to marshal PKCS8 key: %w", err)
}
block := &pem.Block{
Type: "PRIVATE KEY",
Bytes: b,
}
return pem.EncodeToMemory(block), nil
}
func (cfg *NodeConfig) UnmarshalPEMPrivateKey(b []byte) error {
p, _ := pem.Decode(b)
if p == nil {
return fmt.Errorf("failed to parse PEM file")
}
if p.Type != "PRIVATE KEY" {
return fmt.Errorf("unexpected PEM type %q", p.Type)
}
k, err := x509.ParsePKCS8PrivateKey(p.Bytes)
if err != nil {
return fmt.Errorf("failed to unmarshal PKCS8 key: %w", err)
}
key, ok := k.(ed25519.PrivateKey)
if !ok {
return fmt.Errorf("private key must be ed25519 key")
}
if len(key) != ed25519.PrivateKeySize {
return fmt.Errorf("unexpected ed25519 private key length")
}
cfg.PrivateKey = KeyBytes(key)
return nil
}
type KeyBytes []byte
func (k KeyBytes) MarshalJSON() ([]byte, error) {
return json.Marshal(hex.EncodeToString(k))
}
func (k *KeyBytes) UnmarshalJSON(b []byte) error {
var s string
var err error
if err = json.Unmarshal(b, &s); err != nil {
return err
}
*k, err = hex.DecodeString(s)
return err
} }

View file

@ -1,54 +1,54 @@
package config package config
import ( import (
"bytes"
"encoding/hex"
"testing" "testing"
) )
func TestConfig_Keys(t *testing.T) { func TestConfig_Keys(t *testing.T) {
var nodeConfig NodeConfig /*
nodeConfig.NewKeys() var nodeConfig NodeConfig
nodeConfig.NewKeys()
publicKey1, err := hex.DecodeString(nodeConfig.PublicKey) publicKey1, err := hex.DecodeString(nodeConfig.PublicKey)
if err != nil { if err != nil {
t.Fatal("can not decode generated public key") t.Fatal("can not decode generated public key")
} }
if len(publicKey1) == 0 { if len(publicKey1) == 0 {
t.Fatal("empty public key generated") t.Fatal("empty public key generated")
} }
privateKey1, err := hex.DecodeString(nodeConfig.PrivateKey) privateKey1, err := hex.DecodeString(nodeConfig.PrivateKey)
if err != nil { if err != nil {
t.Fatal("can not decode generated private key") t.Fatal("can not decode generated private key")
} }
if len(privateKey1) == 0 { if len(privateKey1) == 0 {
t.Fatal("empty private key generated") t.Fatal("empty private key generated")
} }
nodeConfig.NewKeys() nodeConfig.NewKeys()
publicKey2, err := hex.DecodeString(nodeConfig.PublicKey) publicKey2, err := hex.DecodeString(nodeConfig.PublicKey)
if err != nil { if err != nil {
t.Fatal("can not decode generated public key") t.Fatal("can not decode generated public key")
} }
if bytes.Equal(publicKey2, publicKey1) { if bytes.Equal(publicKey2, publicKey1) {
t.Fatal("same public key generated") t.Fatal("same public key generated")
} }
privateKey2, err := hex.DecodeString(nodeConfig.PrivateKey) privateKey2, err := hex.DecodeString(nodeConfig.PrivateKey)
if err != nil { if err != nil {
t.Fatal("can not decode generated private key") t.Fatal("can not decode generated private key")
} }
if bytes.Equal(privateKey2, privateKey1) { if bytes.Equal(privateKey2, privateKey1) {
t.Fatal("same private key generated") t.Fatal("same private key generated")
} }
*/
} }

34
src/config/defaults.go Normal file
View file

@ -0,0 +1,34 @@
package config
var defaultConfig = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/config.defaultConfig=/path/to/config
var defaultAdminListen = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/config.defaultAdminListen=unix://path/to/sock'
// Defines which parameters are expected by default for configuration on a
// specific platform. These values are populated in the relevant defaults_*.go
// for the platform being targeted. They must be set.
type platformDefaultParameters struct {
// Admin socket
DefaultAdminListen string
// Configuration (used for yggdrasilctl)
DefaultConfigFile string
// Multicast interfaces
DefaultMulticastInterfaces []MulticastInterfaceConfig
// TUN
MaximumIfMTU uint64
DefaultIfMTU uint64
DefaultIfName string
}
func GetDefaults() platformDefaultParameters {
defaults := getDefaults()
if defaultConfig != "" {
defaults.DefaultConfigFile = defaultConfig
}
if defaultAdminListen != "" {
defaults.DefaultAdminListen = defaultAdminListen
}
return defaults
}

View file

@ -1,7 +1,7 @@
//go:build darwin //go:build darwin
// +build darwin // +build darwin
package defaults package config
// Sane defaults for the macOS/Darwin platform. The "default" options may be // Sane defaults for the macOS/Darwin platform. The "default" options may be
// may be replaced by the running configuration. // may be replaced by the running configuration.
@ -17,6 +17,7 @@ func getDefaults() platformDefaultParameters {
DefaultMulticastInterfaces: []MulticastInterfaceConfig{ DefaultMulticastInterfaces: []MulticastInterfaceConfig{
{Regex: "en.*", Beacon: true, Listen: true}, {Regex: "en.*", Beacon: true, Listen: true},
{Regex: "bridge.*", Beacon: true, Listen: true}, {Regex: "bridge.*", Beacon: true, Listen: true},
{Regex: "awdl0", Beacon: false, Listen: false},
}, },
// TUN // TUN

View file

@ -1,7 +1,7 @@
//go:build freebsd //go:build freebsd
// +build freebsd // +build freebsd
package defaults package config
// Sane defaults for the BSD platforms. The "default" options may be // Sane defaults for the BSD platforms. The "default" options may be
// may be replaced by the running configuration. // may be replaced by the running configuration.

View file

@ -1,7 +1,7 @@
//go:build linux //go:build linux
// +build linux // +build linux
package defaults package config
// Sane defaults for the Linux platform. The "default" options may be // Sane defaults for the Linux platform. The "default" options may be
// may be replaced by the running configuration. // may be replaced by the running configuration.

View file

@ -1,7 +1,7 @@
//go:build openbsd //go:build openbsd
// +build openbsd // +build openbsd
package defaults package config
// Sane defaults for the BSD platforms. The "default" options may be // Sane defaults for the BSD platforms. The "default" options may be
// may be replaced by the running configuration. // may be replaced by the running configuration.

View file

@ -1,7 +1,7 @@
//go:build !linux && !darwin && !windows && !openbsd && !freebsd //go:build !linux && !darwin && !windows && !openbsd && !freebsd
// +build !linux,!darwin,!windows,!openbsd,!freebsd // +build !linux,!darwin,!windows,!openbsd,!freebsd
package defaults package config
// Sane defaults for the other platforms. The "default" options may be // Sane defaults for the other platforms. The "default" options may be
// may be replaced by the running configuration. // may be replaced by the running configuration.

View file

@ -1,7 +1,7 @@
//go:build windows //go:build windows
// +build windows // +build windows
package defaults package config
// Sane defaults for the Windows platform. The "default" options may be // Sane defaults for the Windows platform. The "default" options may be
// may be replaced by the running configuration. // may be replaced by the running configuration.

View file

@ -3,43 +3,54 @@ package core
import ( import (
"crypto/ed25519" "crypto/ed25519"
"encoding/json" "encoding/json"
"fmt"
"net" "net"
"net/url" "net/url"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/Arceliar/phony" "github.com/Arceliar/phony"
"github.com/Arceliar/ironwood/network"
"github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/address"
) )
type SelfInfo struct { type SelfInfo struct {
Key ed25519.PublicKey Key ed25519.PublicKey
Root ed25519.PublicKey RoutingEntries uint64
Coords []uint64
} }
type PeerInfo struct { type PeerInfo struct {
Key ed25519.PublicKey URI string
Root ed25519.PublicKey Up bool
Coords []uint64 Inbound bool
Port uint64 LastError error
Priority uint8 LastErrorTime time.Time
Remote string Key ed25519.PublicKey
RXBytes uint64 Root ed25519.PublicKey
TXBytes uint64 Coords []uint64
Uptime time.Duration Port uint64
Priority uint8
Cost uint64
RXBytes uint64
TXBytes uint64
RXRate uint64
TXRate uint64
Uptime time.Duration
Latency time.Duration
} }
type DHTEntryInfo struct { type TreeEntryInfo struct {
Key ed25519.PublicKey Key ed25519.PublicKey
Port uint64 Parent ed25519.PublicKey
Rest uint64 Sequence uint64
//Port uint64
//Rest uint64
} }
type PathEntryInfo struct { type PathEntryInfo struct {
Key ed25519.PublicKey Key ed25519.PublicKey
Path []uint64 Path []uint64
Sequence uint64
} }
type SessionInfo struct { type SessionInfo struct {
@ -53,55 +64,63 @@ func (c *Core) GetSelf() SelfInfo {
var self SelfInfo var self SelfInfo
s := c.PacketConn.PacketConn.Debug.GetSelf() s := c.PacketConn.PacketConn.Debug.GetSelf()
self.Key = s.Key self.Key = s.Key
self.Root = s.Root self.RoutingEntries = s.RoutingEntries
self.Coords = s.Coords
return self return self
} }
func (c *Core) GetPeers() []PeerInfo { func (c *Core) GetPeers() []PeerInfo {
var peers []PeerInfo peers := []PeerInfo{}
names := make(map[net.Conn]string) conns := map[net.Conn]network.DebugPeerInfo{}
iwpeers := c.PacketConn.PacketConn.Debug.GetPeers()
for _, p := range iwpeers {
conns[p.Conn] = p
}
phony.Block(&c.links, func() { phony.Block(&c.links, func() {
for _, info := range c.links._links { for info, state := range c.links._links {
if info == nil { var peerinfo PeerInfo
continue var conn net.Conn
peerinfo.URI = info.uri
peerinfo.LastError = state._err
peerinfo.LastErrorTime = state._errtime
if c := state._conn; c != nil {
conn = c
peerinfo.Up = true
peerinfo.Inbound = state.linkType == linkTypeIncoming
peerinfo.RXBytes = atomic.LoadUint64(&c.rx)
peerinfo.TXBytes = atomic.LoadUint64(&c.tx)
peerinfo.RXRate = atomic.LoadUint64(&c.rxrate)
peerinfo.TXRate = atomic.LoadUint64(&c.txrate)
peerinfo.Uptime = time.Since(c.up)
} }
names[info.conn] = info.lname if p, ok := conns[conn]; ok {
peerinfo.Key = p.Key
peerinfo.Root = p.Root
peerinfo.Port = p.Port
peerinfo.Priority = p.Priority
peerinfo.Latency = p.Latency
peerinfo.Cost = p.Cost
}
peers = append(peers, peerinfo)
} }
}) })
ps := c.PacketConn.PacketConn.Debug.GetPeers()
for _, p := range ps {
var info PeerInfo
info.Key = p.Key
info.Root = p.Root
info.Coords = p.Coords
info.Port = p.Port
info.Priority = p.Priority
info.Remote = p.Conn.RemoteAddr().String()
if name := names[p.Conn]; name != "" {
info.Remote = name
}
if linkconn, ok := p.Conn.(*linkConn); ok {
info.RXBytes = atomic.LoadUint64(&linkconn.rx)
info.TXBytes = atomic.LoadUint64(&linkconn.tx)
info.Uptime = time.Since(linkconn.up)
}
peers = append(peers, info)
}
return peers return peers
} }
func (c *Core) GetDHT() []DHTEntryInfo { func (c *Core) GetTree() []TreeEntryInfo {
var dhts []DHTEntryInfo var trees []TreeEntryInfo
ds := c.PacketConn.PacketConn.Debug.GetDHT() ts := c.PacketConn.PacketConn.Debug.GetTree()
for _, d := range ds { for _, t := range ts {
var info DHTEntryInfo var info TreeEntryInfo
info.Key = d.Key info.Key = t.Key
info.Port = d.Port info.Parent = t.Parent
info.Rest = d.Rest info.Sequence = t.Sequence
dhts = append(dhts, info) //info.Port = d.Port
//info.Rest = d.Rest
trees = append(trees, info)
} }
return dhts return trees
} }
func (c *Core) GetPaths() []PathEntryInfo { func (c *Core) GetPaths() []PathEntryInfo {
@ -110,6 +129,7 @@ func (c *Core) GetPaths() []PathEntryInfo {
for _, p := range ps { for _, p := range ps {
var info PathEntryInfo var info PathEntryInfo
info.Key = p.Key info.Key = p.Key
info.Sequence = p.Sequence
info.Path = p.Path info.Path = p.Path
paths = append(paths, info) paths = append(paths, info)
} }
@ -134,16 +154,14 @@ func (c *Core) GetSessions() []SessionInfo {
// parsed from a string of the form e.g. "tcp://a.b.c.d:e". In the case of a // parsed from a string of the form e.g. "tcp://a.b.c.d:e". In the case of a
// link-local address, the interface should be provided as the second argument. // link-local address, the interface should be provided as the second argument.
func (c *Core) Listen(u *url.URL, sintf string) (*Listener, error) { func (c *Core) Listen(u *url.URL, sintf string) (*Listener, error) {
switch u.Scheme { return c.links.listen(u, sintf, false)
case "tcp": }
return c.links.tcp.listen(u, sintf)
case "tls": // ListenLocal starts a listener, like the Listen function, but is used for
return c.links.tls.listen(u, sintf) // more trustworthy situations where you want to ignore AllowedPublicKeys, i.e.
case "unix": // with multicast listeners.
return c.links.unix.listen(u, sintf) func (c *Core) ListenLocal(u *url.URL, sintf string) (*Listener, error) {
default: return c.links.listen(u, sintf, true)
return nil, fmt.Errorf("unrecognised scheme %q", u.Scheme)
}
} }
// Address gets the IPv6 address of the Yggdrasil node. This is always a /128 // Address gets the IPv6 address of the Yggdrasil node. This is always a /128
@ -182,49 +200,14 @@ func (c *Core) SetLogger(log Logger) {
// //
// This adds the peer to the peer list, so that they will be called again if the // This adds the peer to the peer list, so that they will be called again if the
// connection drops. // connection drops.
func (c *Core) AddPeer(uri string, sourceInterface string) error { func (c *Core) AddPeer(u *url.URL, sintf string) error {
var known bool return c.links.add(u, sintf, linkTypePersistent)
phony.Block(c, func() {
_, known = c.config._peers[Peer{uri, sourceInterface}]
})
if known {
return fmt.Errorf("peer already configured")
}
u, err := url.Parse(uri)
if err != nil {
return err
}
info, err := c.links.call(u, sourceInterface, nil)
if err != nil {
return err
}
phony.Block(c, func() {
c.config._peers[Peer{uri, sourceInterface}] = &info
})
return nil
} }
// RemovePeer removes a peer. The peer should be specified in URI format, see AddPeer. // RemovePeer removes a peer. The peer should be specified in URI format, see AddPeer.
// The peer is not disconnected immediately. // The peer is not disconnected immediately.
func (c *Core) RemovePeer(uri string, sourceInterface string) error { func (c *Core) RemovePeer(u *url.URL, sintf string) error {
var err error return c.links.remove(u, sintf, linkTypePersistent)
phony.Block(c, func() {
peer := Peer{uri, sourceInterface}
linkInfo, ok := c.config._peers[peer]
if !ok {
err = fmt.Errorf("peer not configured")
return
}
if ok && linkInfo != nil {
c.links.Act(nil, func() {
if link := c.links._links[*linkInfo]; link != nil {
_ = link.close()
}
})
}
delete(c.config._peers, peer)
})
return err
} }
// CallPeer calls a peer once. This should be specified in the peer URI format, // CallPeer calls a peer once. This should be specified in the peer URI format,
@ -236,8 +219,7 @@ func (c *Core) RemovePeer(uri string, sourceInterface string) error {
// This does not add the peer to the peer list, so if the connection drops, the // This does not add the peer to the peer list, so if the connection drops, the
// peer will not be called again automatically. // peer will not be called again automatically.
func (c *Core) CallPeer(u *url.URL, sintf string) error { func (c *Core) CallPeer(u *url.URL, sintf string) error {
_, err := c.links.call(u, sintf, nil) return c.links.add(u, sintf, linkTypeEphemeral)
return err
} }
func (c *Core) PublicKey() ed25519.PublicKey { func (c *Core) PublicKey() ed25519.PublicKey {
@ -274,8 +256,8 @@ func (c *Core) SetAdmin(a AddHandler) error {
return err return err
} }
if err := a.AddHandler( if err := a.AddHandler(
"debug_remoteGetDHT", "Debug use only", []string{"key"}, "debug_remoteGetTree", "Debug use only", []string{"key"},
c.proto.getDHTHandler, c.proto.getTreeHandler,
); err != nil { ); err != nil {
return err return err
} }

View file

@ -3,6 +3,7 @@ package core
import ( import (
"context" "context"
"crypto/ed25519" "crypto/ed25519"
"crypto/tls"
"fmt" "fmt"
"io" "io"
"net" "net"
@ -10,10 +11,12 @@ import (
"time" "time"
iwe "github.com/Arceliar/ironwood/encrypted" iwe "github.com/Arceliar/ironwood/encrypted"
iwn "github.com/Arceliar/ironwood/network"
iwt "github.com/Arceliar/ironwood/types" iwt "github.com/Arceliar/ironwood/types"
"github.com/Arceliar/phony" "github.com/Arceliar/phony"
"github.com/gologme/log" "github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/version" "github.com/yggdrasil-network/yggdrasil-go/src/version"
) )
@ -34,49 +37,88 @@ type Core struct {
log Logger log Logger
addPeerTimer *time.Timer addPeerTimer *time.Timer
config struct { config struct {
_peers map[Peer]*linkInfo // configurable after startup tls *tls.Config // immutable after startup
//_peers map[Peer]*linkInfo // configurable after startup
_listeners map[ListenAddress]struct{} // configurable after startup _listeners map[ListenAddress]struct{} // configurable after startup
peerFilter func(ip net.IP) bool // immutable after startup
nodeinfo NodeInfo // immutable after startup nodeinfo NodeInfo // immutable after startup
nodeinfoPrivacy NodeInfoPrivacy // immutable after startup nodeinfoPrivacy NodeInfoPrivacy // immutable after startup
_allowedPublicKeys map[[32]byte]struct{} // configurable after startup _allowedPublicKeys map[[32]byte]struct{} // configurable after startup
} }
pathNotify func(ed25519.PublicKey)
} }
func New(secret ed25519.PrivateKey, logger Logger, opts ...SetupOption) (*Core, error) { func New(cert *tls.Certificate, logger Logger, opts ...SetupOption) (*Core, error) {
c := &Core{ c := &Core{
log: logger, log: logger,
} }
c.ctx, c.cancel = context.WithCancel(context.Background())
if c.log == nil {
c.log = log.New(io.Discard, "", 0)
}
if name := version.BuildName(); name != "unknown" { if name := version.BuildName(); name != "unknown" {
c.log.Infoln("Build name:", name) c.log.Infoln("Build name:", name)
} }
if version := version.BuildVersion(); version != "unknown" { if version := version.BuildVersion(); version != "unknown" {
c.log.Infoln("Build version:", version) c.log.Infoln("Build version:", version)
} }
c.ctx, c.cancel = context.WithCancel(context.Background())
// Take a copy of the private key so that it is in our own memory space.
if len(secret) != ed25519.PrivateKeySize {
return nil, fmt.Errorf("private key is incorrect length")
}
c.secret = make(ed25519.PrivateKey, ed25519.PrivateKeySize)
copy(c.secret, secret)
c.public = secret.Public().(ed25519.PublicKey)
var err error var err error
if c.PacketConn, err = iwe.NewPacketConn(c.secret); err != nil {
return nil, fmt.Errorf("error creating encryption: %w", err)
}
c.config._peers = map[Peer]*linkInfo{}
c.config._listeners = map[ListenAddress]struct{}{} c.config._listeners = map[ListenAddress]struct{}{}
c.config._allowedPublicKeys = map[[32]byte]struct{}{} c.config._allowedPublicKeys = map[[32]byte]struct{}{}
for _, opt := range opts { for _, opt := range opts {
c._applyOption(opt) switch opt.(type) {
case Peer, ListenAddress:
// We can't do peers yet as the links aren't set up.
continue
default:
if err = c._applyOption(opt); err != nil {
return nil, fmt.Errorf("failed to apply configuration option %T: %w", opt, err)
}
}
} }
if c.log == nil { if cert == nil || cert.PrivateKey == nil {
c.log = log.New(io.Discard, "", 0) return nil, fmt.Errorf("no private key supplied")
}
var ok bool
if c.secret, ok = cert.PrivateKey.(ed25519.PrivateKey); !ok {
return nil, fmt.Errorf("private key must be ed25519")
}
if len(c.secret) != ed25519.PrivateKeySize {
return nil, fmt.Errorf("private key is incorrect length")
}
c.public = c.secret.Public().(ed25519.PublicKey)
if c.config.tls, err = c.generateTLSConfig(cert); err != nil {
return nil, fmt.Errorf("error generating TLS config: %w", err)
}
keyXform := func(key ed25519.PublicKey) ed25519.PublicKey {
return address.SubnetForKey(key).GetKey()
}
if c.PacketConn, err = iwe.NewPacketConn(
c.secret,
iwn.WithBloomTransform(keyXform),
iwn.WithPeerMaxMessageSize(65535*2),
iwn.WithPathNotify(c.doPathNotify),
); err != nil {
return nil, fmt.Errorf("error creating encryption: %w", err)
} }
c.proto.init(c) c.proto.init(c)
if err := c.links.init(c); err != nil { if err := c.links.init(c); err != nil {
return nil, fmt.Errorf("error initialising links: %w", err) return nil, fmt.Errorf("error initialising links: %w", err)
} }
for _, opt := range opts {
switch opt.(type) {
case Peer, ListenAddress:
// Now do the peers and listeners.
if err = c._applyOption(opt); err != nil {
return nil, fmt.Errorf("failed to apply configuration option %T: %w", opt, err)
}
default:
continue
}
}
if err := c.proto.nodeinfo.setNodeInfo(c.config.nodeinfo, bool(c.config.nodeinfoPrivacy)); err != nil { if err := c.proto.nodeinfo.setNodeInfo(c.config.nodeinfo, bool(c.config.nodeinfoPrivacy)); err != nil {
return nil, fmt.Errorf("error setting node info: %w", err) return nil, fmt.Errorf("error setting node info: %w", err)
} }
@ -86,46 +128,22 @@ func New(secret ed25519.PrivateKey, logger Logger, opts ...SetupOption) (*Core,
c.log.Errorf("Invalid listener URI %q specified, ignoring\n", listenaddr) c.log.Errorf("Invalid listener URI %q specified, ignoring\n", listenaddr)
continue continue
} }
if _, err = c.links.listen(u, ""); err != nil { if _, err = c.links.listen(u, "", false); err != nil {
c.log.Errorf("Failed to start listener %q: %s\n", listenaddr, err) c.log.Errorf("Failed to start listener %q: %s\n", listenaddr, err)
} }
} }
c.Act(nil, c._addPeerLoop)
return c, nil return c, nil
} }
// If any static peers were provided in the configuration above then we should
// configure them. The loop ensures that disconnected peers will eventually
// be reconnected with.
func (c *Core) _addPeerLoop() {
select {
case <-c.ctx.Done():
return
default:
}
// Add peers from the Peers section
for peer := range c.config._peers {
go func(peer string, intf string) {
u, err := url.Parse(peer)
if err != nil {
c.log.Errorln("Failed to parse peer url:", peer, err)
}
if err := c.CallPeer(u, intf); err != nil {
c.log.Errorln("Failed to add peer:", err)
}
}(peer.URI, peer.SourceInterface) // TODO: this should be acted and not in a goroutine?
}
c.addPeerTimer = time.AfterFunc(time.Minute, func() {
c.Act(nil, c._addPeerLoop)
})
}
func (c *Core) RetryPeersNow() { func (c *Core) RetryPeersNow() {
if c.addPeerTimer != nil && !c.addPeerTimer.Stop() { phony.Block(&c.links, func() {
<-c.addPeerTimer.C for _, l := range c.links._links {
} select {
c.Act(nil, c._addPeerLoop) case l.kick <- struct{}{}:
default:
}
}
})
} }
// Stop shuts down the Yggdrasil node. // Stop shuts down the Yggdrasil node.
@ -151,11 +169,16 @@ func (c *Core) _close() error {
func (c *Core) MTU() uint64 { func (c *Core) MTU() uint64 {
const sessionTypeOverhead = 1 const sessionTypeOverhead = 1
return c.PacketConn.MTU() - sessionTypeOverhead MTU := c.PacketConn.MTU() - sessionTypeOverhead
if MTU > 65535 {
MTU = 65535
}
return MTU
} }
func (c *Core) ReadFrom(p []byte) (n int, from net.Addr, err error) { func (c *Core) ReadFrom(p []byte) (n int, from net.Addr, err error) {
buf := make([]byte, c.PacketConn.MTU(), 65535) buf := allocBytes(int(c.PacketConn.MTU()))
defer freeBytes(buf)
for { for {
bs := buf bs := buf
n, from, err = c.PacketConn.ReadFrom(bs) n, from, err = c.PacketConn.ReadFrom(bs)
@ -189,7 +212,8 @@ func (c *Core) ReadFrom(p []byte) (n int, from net.Addr, err error) {
} }
func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) { func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) {
buf := make([]byte, 0, 65535) buf := allocBytes(0)
defer func() { freeBytes(buf) }()
buf = append(buf, typeSessionTraffic) buf = append(buf, typeSessionTraffic)
buf = append(buf, p...) buf = append(buf, p...)
n, err = c.PacketConn.WriteTo(buf, addr) n, err = c.PacketConn.WriteTo(buf, addr)
@ -199,6 +223,20 @@ func (c *Core) WriteTo(p []byte, addr net.Addr) (n int, err error) {
return return
} }
func (c *Core) doPathNotify(key ed25519.PublicKey) {
c.Act(nil, func() {
if c.pathNotify != nil {
c.pathNotify(key)
}
})
}
func (c *Core) SetPathNotify(notify func(ed25519.PublicKey)) {
c.Act(nil, func() {
c.pathNotify = notify
})
}
type Logger interface { type Logger interface {
Printf(string, ...interface{}) Printf(string, ...interface{})
Println(...interface{}) Println(...interface{})
@ -210,4 +248,5 @@ type Logger interface {
Errorln(...interface{}) Errorln(...interface{})
Debugf(string, ...interface{}) Debugf(string, ...interface{})
Debugln(...interface{}) Debugln(...interface{})
Traceln(...interface{})
} }

View file

@ -2,14 +2,14 @@ package core
import ( import (
"bytes" "bytes"
"crypto/ed25519" "crypto/rand"
"math/rand"
"net/url" "net/url"
"os" "os"
"testing" "testing"
"time" "time"
"github.com/gologme/log" "github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
) )
// GetLoggerWithPrefix creates a new logger instance with prefix. // GetLoggerWithPrefix creates a new logger instance with prefix.
@ -25,33 +25,65 @@ func GetLoggerWithPrefix(prefix string, verbose bool) *log.Logger {
return l return l
} }
func require_NoError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatal(err)
}
}
func require_Equal[T comparable](t *testing.T, a, b T) {
t.Helper()
if a != b {
t.Fatalf("%v != %v", a, b)
}
}
func require_True(t *testing.T, a bool) {
t.Helper()
if !a {
t.Fatal("expected true")
}
}
// CreateAndConnectTwo creates two nodes. nodeB connects to nodeA. // CreateAndConnectTwo creates two nodes. nodeB connects to nodeA.
// Verbosity flag is passed to logger. // Verbosity flag is passed to logger.
func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) { func CreateAndConnectTwo(t testing.TB, verbose bool) (nodeA *Core, nodeB *Core) {
var err error var err error
var skA, skB ed25519.PrivateKey
if _, skA, err = ed25519.GenerateKey(nil); err != nil { cfgA, cfgB := config.GenerateConfig(), config.GenerateConfig()
if err = cfgA.GenerateSelfSignedCertificate(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, skB, err = ed25519.GenerateKey(nil); err != nil { if err = cfgB.GenerateSelfSignedCertificate(); err != nil {
t.Fatal(err)
}
logger := GetLoggerWithPrefix("", false)
if nodeA, err = New(skA, logger, ListenAddress("tcp://127.0.0.1:0")); err != nil {
t.Fatal(err)
}
if nodeB, err = New(skB, logger, ListenAddress("tcp://127.0.0.1:0")); err != nil {
t.Fatal(err) t.Fatal(err)
} }
u, err := url.Parse("tcp://" + nodeA.links.tcp.getAddr().String()) logger := GetLoggerWithPrefix("", false)
logger.EnableLevel("debug")
if nodeA, err = New(cfgA.Certificate, logger); err != nil {
t.Fatal(err)
}
if nodeB, err = New(cfgB.Certificate, logger); err != nil {
t.Fatal(err)
}
nodeAListenURL, err := url.Parse("tcp://localhost:0")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = nodeB.CallPeer(u, "") nodeAListener, err := nodeA.Listen(nodeAListenURL, "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
nodeAURL, err := url.Parse("tcp://" + nodeAListener.Addr().String())
if err != nil {
t.Fatal(err)
}
if err = nodeB.CallPeer(nodeAURL, ""); err != nil {
t.Fatal(err)
}
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
@ -70,7 +102,13 @@ func WaitConnected(nodeA, nodeB *Core) bool {
// It may take up to 3 seconds, but let's wait 5. // It may take up to 3 seconds, but let's wait 5.
for i := 0; i < 50; i++ { for i := 0; i < 50; i++ {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
if len(nodeA.GetPeers()) > 0 && len(nodeB.GetPeers()) > 0 { /*
if len(nodeA.GetPeers()) > 0 && len(nodeB.GetPeers()) > 0 {
return true
}
*/
if len(nodeA.GetTree()) > 1 && len(nodeB.GetTree()) > 1 {
time.Sleep(3 * time.Second) // FIXME hack, there's still stuff happening internally
return true return true
} }
} }
@ -129,7 +167,7 @@ func TestCore_Start_Transfer(t *testing.T) {
// Send // Send
msg := make([]byte, msgLen) msg := make([]byte, msgLen)
rand.Read(msg[40:]) _, _ = rand.Read(msg[40:])
msg[0] = 0x60 msg[0] = 0x60
copy(msg[8:24], nodeB.Address()) copy(msg[8:24], nodeB.Address())
copy(msg[24:40], nodeA.Address()) copy(msg[24:40], nodeA.Address())
@ -161,7 +199,7 @@ func BenchmarkCore_Start_Transfer(b *testing.B) {
// Send // Send
msg := make([]byte, msgLen) msg := make([]byte, msgLen)
rand.Read(msg[40:]) _, _ = rand.Read(msg[40:])
msg[0] = 0x60 msg[0] = 0x60
copy(msg[8:24], nodeB.Address()) copy(msg[8:24], nodeB.Address())
copy(msg[24:40], nodeA.Address()) copy(msg[24:40], nodeA.Address())
@ -184,3 +222,69 @@ func BenchmarkCore_Start_Transfer(b *testing.B) {
} }
<-done <-done
} }
func TestAllowedPublicKeys(t *testing.T) {
logger := GetLoggerWithPrefix("", false)
cfgA, cfgB := config.GenerateConfig(), config.GenerateConfig()
require_NoError(t, cfgA.GenerateSelfSignedCertificate())
require_NoError(t, cfgB.GenerateSelfSignedCertificate())
nodeA, err := New(cfgA.Certificate, logger, AllowedPublicKey("abcdef"))
require_NoError(t, err)
defer nodeA.Stop()
nodeB, err := New(cfgB.Certificate, logger)
require_NoError(t, err)
defer nodeB.Stop()
u, err := url.Parse("tcp://localhost:0")
require_NoError(t, err)
l, err := nodeA.Listen(u, "")
require_NoError(t, err)
u, err = url.Parse("tcp://" + l.Addr().String())
require_NoError(t, err)
require_NoError(t, nodeB.AddPeer(u, ""))
time.Sleep(time.Second)
peers := nodeB.GetPeers()
require_Equal(t, len(peers), 1)
require_True(t, !peers[0].Up)
require_True(t, peers[0].LastError != nil)
}
func TestAllowedPublicKeysLocal(t *testing.T) {
logger := GetLoggerWithPrefix("", false)
cfgA, cfgB := config.GenerateConfig(), config.GenerateConfig()
require_NoError(t, cfgA.GenerateSelfSignedCertificate())
require_NoError(t, cfgB.GenerateSelfSignedCertificate())
nodeA, err := New(cfgA.Certificate, logger, AllowedPublicKey("abcdef"))
require_NoError(t, err)
defer nodeA.Stop()
nodeB, err := New(cfgB.Certificate, logger)
require_NoError(t, err)
defer nodeB.Stop()
u, err := url.Parse("tcp://localhost:0")
require_NoError(t, err)
l, err := nodeA.ListenLocal(u, "")
require_NoError(t, err)
u, err = url.Parse("tcp://" + l.Addr().String())
require_NoError(t, err)
require_NoError(t, nodeB.AddPeer(u, ""))
time.Sleep(time.Second)
peers := nodeB.GetPeers()
require_Equal(t, len(peers), 1)
require_True(t, peers[0].Up)
require_True(t, peers[0].LastError == nil)
}

View file

@ -1,6 +1,3 @@
//go:build debug
// +build debug
package core package core
import ( import (
@ -8,28 +5,15 @@ import (
"net/http" "net/http"
_ "net/http/pprof" _ "net/http/pprof"
"os" "os"
"runtime"
"github.com/gologme/log"
) )
// Start the profiler in debug builds, if the required environment variable is set. // Start the profiler if the required environment variable is set.
func init() { func init() {
envVarName := "PPROFLISTEN" envVarName := "PPROFLISTEN"
hostPort := os.Getenv(envVarName) if hostPort := os.Getenv(envVarName); hostPort != "" {
switch {
case hostPort == "":
fmt.Fprintf(os.Stderr, "DEBUG: %s not set, profiler not started.\n", envVarName)
default:
fmt.Fprintf(os.Stderr, "DEBUG: Starting pprof on %s\n", hostPort) fmt.Fprintf(os.Stderr, "DEBUG: Starting pprof on %s\n", hostPort)
go func() { fmt.Println(http.ListenAndServe(hostPort, nil)) }() go func() {
fmt.Fprintf(os.Stderr, "DEBUG: %s", http.ListenAndServe(hostPort, nil))
}()
} }
} }
// Starts the function profiler. This is only supported when built with
// '-tags build'.
func StartProfiler(log *log.Logger) error {
runtime.SetBlockProfileRate(1)
go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }()
return nil
}

File diff suppressed because it is too large Load diff

108
src/core/link_quic.go Normal file
View file

@ -0,0 +1,108 @@
package core
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/url"
"time"
"github.com/Arceliar/phony"
"github.com/quic-go/quic-go"
)
type linkQUIC struct {
phony.Inbox
*links
tlsconfig *tls.Config
quicconfig *quic.Config
}
type linkQUICStream struct {
quic.Connection
quic.Stream
}
type linkQUICListener struct {
*quic.Listener
ch <-chan *linkQUICStream
}
func (l *linkQUICListener) Accept() (net.Conn, error) {
qs := <-l.ch
if qs == nil {
return nil, context.Canceled
}
return qs, nil
}
func (l *links) newLinkQUIC() *linkQUIC {
lt := &linkQUIC{
links: l,
tlsconfig: l.core.config.tls.Clone(),
quicconfig: &quic.Config{
MaxIdleTimeout: time.Minute,
KeepAlivePeriod: time.Second * 20,
TokenStore: quic.NewLRUTokenStore(255, 255),
},
}
return lt
}
func (l *linkQUIC) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
tlsconfig := l.tlsconfig.Clone()
return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
tlsconfig.ServerName = hostname
tlsconfig.MinVersion = tls.VersionTLS12
tlsconfig.MaxVersion = tls.VersionTLS13
hostport := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
qc, err := quic.DialAddr(ctx, hostport, l.tlsconfig, l.quicconfig)
if err != nil {
return nil, err
}
qs, err := qc.OpenStreamSync(ctx)
if err != nil {
return nil, err
}
return &linkQUICStream{
Connection: qc,
Stream: qs,
}, nil
})
}
func (l *linkQUIC) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
ql, err := quic.ListenAddr(url.Host, l.tlsconfig, l.quicconfig)
if err != nil {
return nil, err
}
ch := make(chan *linkQUICStream)
lql := &linkQUICListener{
Listener: ql,
ch: ch,
}
go func() {
for {
qc, err := ql.Accept(ctx)
switch err {
case context.Canceled, context.DeadlineExceeded:
ql.Close()
fallthrough
case quic.ErrServerClosed:
return
case nil:
qs, err := qc.AcceptStream(ctx)
if err != nil {
_ = qc.CloseWithError(1, fmt.Sprintf("stream error: %s", err))
continue
}
ch <- &linkQUICStream{
Connection: qc,
Stream: qs,
}
}
}
}()
return lql, nil
}

View file

@ -1,6 +1,8 @@
package core package core
import ( import (
"context"
"crypto/tls"
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
@ -20,37 +22,46 @@ func (l *links) newLinkSOCKS() *linkSOCKS {
return lt return lt
} }
func (l *linkSOCKS) dial(url *url.URL, options linkOptions) error { func (l *linkSOCKS) dial(_ context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
info := linkInfoFor("socks", "", url.Path) var proxyAuth *proxy.Auth
if l.links.isConnectedTo(info) { if url.User != nil && url.User.Username() != "" {
return nil proxyAuth = &proxy.Auth{
User: url.User.Username(),
}
proxyAuth.Password, _ = url.User.Password()
} }
proxyAuth := &proxy.Auth{} tlsconfig := l.tls.config.Clone()
proxyAuth.User = url.User.Username() return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
proxyAuth.Password, _ = url.User.Password() hostport := net.JoinHostPort(ip.String(), fmt.Sprintf("%d", port))
dialer, err := proxy.SOCKS5("tcp", url.Host, proxyAuth, proxy.Direct) dialer, err := l.tcp.dialerFor(&net.TCPAddr{
if err != nil { IP: ip,
return fmt.Errorf("failed to configure proxy") Port: port,
} }, info.sintf)
pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/") if err != nil {
conn, err := dialer.Dial("tcp", pathtokens[0]) return nil, err
if err != nil { }
return err proxy, err := proxy.SOCKS5("tcp", hostport, proxyAuth, dialer)
} if err != nil {
dial := &linkDial{ return nil, err
url: url, }
} pathtokens := strings.Split(strings.Trim(url.Path, "/"), "/")
return l.handler(dial, info, conn, options, false) conn, err := proxy.Dial("tcp", pathtokens[0])
if err != nil {
return nil, err
}
if url.Scheme == "sockstls" {
tlsconfig.ServerName = hostname
tlsconfig.MinVersion = tls.VersionTLS12
tlsconfig.MaxVersion = tls.VersionTLS13
if sni := options.tlsSNI; sni != "" {
tlsconfig.ServerName = sni
}
conn = tls.Client(conn, tlsconfig)
}
return conn, nil
})
} }
func (l *linkSOCKS) handler(dial *linkDial, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error { func (l *linkSOCKS) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
return l.links.create( return nil, fmt.Errorf("SOCKS listener not supported")
conn, // connection
dial, // connection URL
dial.url.String(), // connection name
info, // connection info
incoming, // not incoming
false, // not forced
options, // connection options
)
} }

View file

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
"strings"
"time" "time"
"github.com/Arceliar/phony" "github.com/Arceliar/phony"
@ -14,117 +13,42 @@ import (
type linkTCP struct { type linkTCP struct {
phony.Inbox phony.Inbox
*links *links
listener *net.ListenConfig listenconfig *net.ListenConfig
_listeners map[*Listener]context.CancelFunc
} }
func (l *links) newLinkTCP() *linkTCP { func (l *links) newLinkTCP() *linkTCP {
lt := &linkTCP{ lt := &linkTCP{
links: l, links: l,
listener: &net.ListenConfig{ listenconfig: &net.ListenConfig{
KeepAlive: -1, KeepAlive: -1,
}, },
_listeners: map[*Listener]context.CancelFunc{},
} }
lt.listener.Control = lt.tcpContext lt.listenconfig.Control = lt.tcpContext
return lt return lt
} }
func (l *linkTCP) dial(url *url.URL, options linkOptions, sintf string) error { func (l *linkTCP) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
addr, err := net.ResolveTCPAddr("tcp", url.Host) return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
if err != nil { addr := &net.TCPAddr{
return err IP: ip,
} Port: port,
dialer, err := l.dialerFor(addr, sintf) }
if err != nil { dialer, err := l.tcp.dialerFor(addr, info.sintf)
return err if err != nil {
} return nil, err
info := linkInfoFor("tcp", sintf, tcpIDFor(dialer.LocalAddr, addr)) }
if l.links.isConnectedTo(info) { return dialer.DialContext(ctx, "tcp", addr.String())
return nil })
}
conn, err := dialer.DialContext(l.core.ctx, "tcp", addr.String())
if err != nil {
return err
}
name := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/")
dial := &linkDial{
url: url,
sintf: sintf,
}
return l.handler(dial, name, info, conn, options, false, false)
} }
func (l *linkTCP) listen(url *url.URL, sintf string) (*Listener, error) { func (l *linkTCP) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
ctx, cancel := context.WithCancel(l.core.ctx)
hostport := url.Host hostport := url.Host
if sintf != "" { if sintf != "" {
if host, port, err := net.SplitHostPort(hostport); err == nil { if host, port, err := net.SplitHostPort(hostport); err == nil {
hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port) hostport = fmt.Sprintf("[%s%%%s]:%s", host, sintf, port)
} }
} }
listener, err := l.listener.Listen(ctx, "tcp", hostport) return l.listenconfig.Listen(ctx, "tcp", hostport)
if err != nil {
cancel()
return nil, err
}
entry := &Listener{
Listener: listener,
closed: make(chan struct{}),
}
phony.Block(l, func() {
l._listeners[entry] = cancel
})
l.core.log.Printf("TCP listener started on %s", listener.Addr())
go func() {
defer phony.Block(l, func() {
delete(l._listeners, entry)
})
for {
conn, err := listener.Accept()
if err != nil {
cancel()
break
}
laddr := conn.LocalAddr().(*net.TCPAddr)
raddr := conn.RemoteAddr().(*net.TCPAddr)
name := fmt.Sprintf("tcp://%s", raddr)
info := linkInfoFor("tcp", sintf, tcpIDFor(laddr, raddr))
if err = l.handler(nil, name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil {
l.core.log.Errorln("Failed to create inbound link:", err)
}
}
_ = listener.Close()
close(entry.closed)
l.core.log.Printf("TCP listener stopped on %s", listener.Addr())
}()
return entry, nil
}
func (l *linkTCP) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error {
return l.links.create(
conn, // connection
dial, // connection URL
name, // connection name
info, // connection info
incoming, // not incoming
force, // not forced
options, // connection options
)
}
// Returns the address of the listener.
func (l *linkTCP) getAddr() *net.TCPAddr {
// TODO: Fix this, because this will currently only give a single address
// to multicast.go, which obviously is not great, but right now multicast.go
// doesn't have the ability to send more than one address in a packet either
var addr *net.TCPAddr
phony.Block(l, func() {
for listener := range l._listeners {
addr = listener.Addr().(*net.TCPAddr)
}
})
return addr
} }
func (l *linkTCP) dialerFor(dst *net.TCPAddr, sintf string) (*net.Dialer, error) { func (l *linkTCP) dialerFor(dst *net.TCPAddr, sintf string) (*net.Dialer, error) {
@ -185,16 +109,3 @@ func (l *linkTCP) dialerFor(dst *net.TCPAddr, sintf string) (*net.Dialer, error)
} }
return dialer, nil return dialer, nil
} }
func tcpIDFor(local net.Addr, remoteAddr *net.TCPAddr) string {
if localAddr, ok := local.(*net.TCPAddr); ok && localAddr.IP.Equal(remoteAddr.IP) {
// Nodes running on the same host — include both the IP and port.
return remoteAddr.String()
}
if remoteAddr.IP.IsLinkLocalUnicast() {
// Nodes discovered via multicast — include the IP only.
return remoteAddr.IP.String()
}
// Nodes connected remotely — include both the IP and port.
return remoteAddr.String()
}

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 return t.tcpContext
} }

View file

@ -12,22 +12,6 @@ import (
// WARNING: This context is used both by net.Dialer and net.Listen in tcp.go // WARNING: This context is used both by net.Dialer and net.Listen in tcp.go
func (t *linkTCP) tcpContext(network, address string, c syscall.RawConn) error { func (t *linkTCP) tcpContext(network, address string, c syscall.RawConn) error {
var control error
var bbr error
control = c.Control(func(fd uintptr) {
bbr = unix.SetsockoptString(int(fd), unix.IPPROTO_TCP, unix.TCP_CONGESTION, "bbr")
})
// Log any errors
if bbr != nil {
t.links.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, SetsockoptString error:", bbr)
}
if control != nil {
t.links.core.log.Debugln("Failed to set tcp_congestion_control to bbr for socket, Control error:", control)
}
// Return nil because errors here are not considered fatal for the connection, it just means congestion control is suboptimal
return nil return nil
} }

View file

@ -1,20 +1,11 @@
package core package core
import ( import (
"bytes"
"context" "context"
"crypto/rand"
"crypto/tls" "crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/pem"
"fmt" "fmt"
"math/big"
"net" "net"
"net/url" "net/url"
"strings"
"time"
"github.com/Arceliar/phony" "github.com/Arceliar/phony"
) )
@ -22,10 +13,9 @@ import (
type linkTLS struct { type linkTLS struct {
phony.Inbox phony.Inbox
*links *links
tcp *linkTCP tcp *linkTCP
listener *net.ListenConfig listener *net.ListenConfig
config *tls.Config config *tls.Config
_listeners map[*Listener]context.CancelFunc
} }
func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS { func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS {
@ -36,49 +26,37 @@ func (l *links) newLinkTLS(tcp *linkTCP) *linkTLS {
Control: tcp.tcpContext, Control: tcp.tcpContext,
KeepAlive: -1, KeepAlive: -1,
}, },
_listeners: map[*Listener]context.CancelFunc{}, config: l.core.config.tls.Clone(),
}
var err error
lt.config, err = lt.generateConfig()
if err != nil {
panic(err)
} }
return lt return lt
} }
func (l *linkTLS) dial(url *url.URL, options linkOptions, sintf, sni string) error { func (l *linkTLS) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
addr, err := net.ResolveTCPAddr("tcp", url.Host)
if err != nil {
return err
}
dialer, err := l.tcp.dialerFor(addr, sintf)
if err != nil {
return err
}
info := linkInfoFor("tls", sintf, tcpIDFor(dialer.LocalAddr, addr))
if l.links.isConnectedTo(info) {
return nil
}
tlsconfig := l.config.Clone() tlsconfig := l.config.Clone()
tlsconfig.ServerName = sni return l.links.findSuitableIP(url, func(hostname string, ip net.IP, port int) (net.Conn, error) {
tlsdialer := &tls.Dialer{ tlsconfig.ServerName = hostname
NetDialer: dialer, tlsconfig.MinVersion = tls.VersionTLS12
Config: tlsconfig, tlsconfig.MaxVersion = tls.VersionTLS13
} if sni := options.tlsSNI; sni != "" {
conn, err := tlsdialer.DialContext(l.core.ctx, "tcp", addr.String()) tlsconfig.ServerName = sni
if err != nil { }
return err addr := &net.TCPAddr{
} IP: ip,
name := strings.TrimRight(strings.SplitN(url.String(), "?", 2)[0], "/") Port: port,
dial := &linkDial{ }
url: url, dialer, err := l.tcp.dialerFor(addr, info.sintf)
sintf: sintf, if err != nil {
} return nil, err
return l.handler(dial, name, info, conn, options, false, false) }
tlsdialer := &tls.Dialer{
NetDialer: dialer,
Config: tlsconfig,
}
return tlsdialer.DialContext(ctx, "tcp", addr.String())
})
} }
func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) { func (l *linkTLS) listen(ctx context.Context, url *url.URL, sintf string) (net.Listener, error) {
ctx, cancel := context.WithCancel(l.core.ctx)
hostport := url.Host hostport := url.Host
if sintf != "" { if sintf != "" {
if host, port, err := net.SplitHostPort(hostport); err == nil { if host, port, err := net.SplitHostPort(hostport); err == nil {
@ -87,88 +65,8 @@ func (l *linkTLS) listen(url *url.URL, sintf string) (*Listener, error) {
} }
listener, err := l.listener.Listen(ctx, "tcp", hostport) listener, err := l.listener.Listen(ctx, "tcp", hostport)
if err != nil { if err != nil {
cancel()
return nil, err return nil, err
} }
tlslistener := tls.NewListener(listener, l.config) tlslistener := tls.NewListener(listener, l.config)
entry := &Listener{ return tlslistener, nil
Listener: tlslistener,
closed: make(chan struct{}),
}
phony.Block(l, func() {
l._listeners[entry] = cancel
})
l.core.log.Printf("TLS listener started on %s", listener.Addr())
go func() {
defer phony.Block(l, func() {
delete(l._listeners, entry)
})
for {
conn, err := tlslistener.Accept()
if err != nil {
cancel()
break
}
laddr := conn.LocalAddr().(*net.TCPAddr)
raddr := conn.RemoteAddr().(*net.TCPAddr)
name := fmt.Sprintf("tls://%s", raddr)
info := linkInfoFor("tls", sintf, tcpIDFor(laddr, raddr))
if err = l.handler(nil, name, info, conn, linkOptionsForListener(url), true, raddr.IP.IsLinkLocalUnicast()); err != nil {
l.core.log.Errorln("Failed to create inbound link:", err)
}
}
_ = tlslistener.Close()
close(entry.closed)
l.core.log.Printf("TLS listener stopped on %s", listener.Addr())
}()
return entry, nil
}
// RFC5280 section 4.1.2.5
var notAfterNeverExpires = time.Date(9999, time.December, 31, 23, 59, 59, 0, time.UTC)
func (l *linkTLS) generateConfig() (*tls.Config, error) {
certBuf := &bytes.Buffer{}
cert := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: hex.EncodeToString(l.links.core.public[:]),
},
NotBefore: time.Now(),
NotAfter: notAfterNeverExpires,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
certbytes, err := x509.CreateCertificate(rand.Reader, &cert, &cert, l.links.core.public, l.links.core.secret)
if err != nil {
return nil, err
}
if err := pem.Encode(certBuf, &pem.Block{
Type: "CERTIFICATE",
Bytes: certbytes,
}); err != nil {
return nil, err
}
rootCAs := x509.NewCertPool()
rootCAs.AppendCertsFromPEM(certbytes)
return &tls.Config{
RootCAs: rootCAs,
Certificates: []tls.Certificate{
{
Certificate: [][]byte{certbytes},
PrivateKey: l.links.core.secret,
},
},
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS13,
}, nil
}
func (l *linkTLS) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming, force bool) error {
return l.tcp.handler(dial, name, info, conn, options, incoming, force)
} }

View file

@ -12,9 +12,8 @@ import (
type linkUNIX struct { type linkUNIX struct {
phony.Inbox phony.Inbox
*links *links
dialer *net.Dialer dialer *net.Dialer
listener *net.ListenConfig listener *net.ListenConfig
_listeners map[*Listener]context.CancelFunc
} }
func (l *links) newLinkUNIX() *linkUNIX { func (l *links) newLinkUNIX() *linkUNIX {
@ -27,75 +26,18 @@ func (l *links) newLinkUNIX() *linkUNIX {
listener: &net.ListenConfig{ listener: &net.ListenConfig{
KeepAlive: -1, KeepAlive: -1,
}, },
_listeners: map[*Listener]context.CancelFunc{},
} }
return lt return lt
} }
func (l *linkUNIX) dial(url *url.URL, options linkOptions, _ string) error { func (l *linkUNIX) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) {
info := linkInfoFor("unix", "", url.Path)
if l.links.isConnectedTo(info) {
return nil
}
addr, err := net.ResolveUnixAddr("unix", url.Path) addr, err := net.ResolveUnixAddr("unix", url.Path)
if err != nil { if err != nil {
return err
}
conn, err := l.dialer.DialContext(l.core.ctx, "unix", addr.String())
if err != nil {
return err
}
dial := &linkDial{
url: url,
}
return l.handler(dial, url.String(), info, conn, options, false)
}
func (l *linkUNIX) listen(url *url.URL, _ string) (*Listener, error) {
ctx, cancel := context.WithCancel(l.core.ctx)
listener, err := l.listener.Listen(ctx, "unix", url.Path)
if err != nil {
cancel()
return nil, err return nil, err
} }
entry := &Listener{ return l.dialer.DialContext(ctx, "unix", addr.String())
Listener: listener,
closed: make(chan struct{}),
}
phony.Block(l, func() {
l._listeners[entry] = cancel
})
l.core.log.Printf("UNIX listener started on %s", listener.Addr())
go func() {
defer phony.Block(l, func() {
delete(l._listeners, entry)
})
for {
conn, err := listener.Accept()
if err != nil {
cancel()
break
}
info := linkInfoFor("unix", "", url.String())
if err = l.handler(nil, url.String(), info, conn, linkOptionsForListener(url), true); err != nil {
l.core.log.Errorln("Failed to create inbound link:", err)
}
}
_ = listener.Close()
close(entry.closed)
l.core.log.Printf("UNIX listener stopped on %s", listener.Addr())
}()
return entry, nil
} }
func (l *linkUNIX) handler(dial *linkDial, name string, info linkInfo, conn net.Conn, options linkOptions, incoming bool) error { func (l *linkUNIX) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) {
return l.links.create( return l.listener.Listen(ctx, "unix", url.Path)
conn, // connection
dial, // connection URL
name, // connection name
info, // connection info
incoming, // not incoming
false, // not forced
options, // connection options
)
} }

148
src/core/link_ws.go Normal file
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

@ -2,14 +2,31 @@ package core
import ( import (
"crypto/ed25519" "crypto/ed25519"
"fmt"
"net"
"net/url"
) )
func (c *Core) _applyOption(opt SetupOption) { func (c *Core) _applyOption(opt SetupOption) (err error) {
switch v := opt.(type) { switch v := opt.(type) {
case Peer: case Peer:
c.config._peers[v] = nil u, err := url.Parse(v.URI)
if err != nil {
return fmt.Errorf("unable to parse peering URI: %w", err)
}
err = c.links.add(u, v.SourceInterface, linkTypePersistent)
switch err {
case ErrLinkAlreadyConfigured:
// Don't return this error, otherwise we'll panic at startup
// if there are multiple of the same peer configured
return nil
default:
return err
}
case ListenAddress: case ListenAddress:
c.config._listeners[v] = struct{}{} c.config._listeners[v] = struct{}{}
case PeerFilter:
c.config.peerFilter = v
case NodeInfo: case NodeInfo:
c.config.nodeinfo = v c.config.nodeinfo = v
case NodeInfoPrivacy: case NodeInfoPrivacy:
@ -19,6 +36,7 @@ func (c *Core) _applyOption(opt SetupOption) {
copy(pk[:], v) copy(pk[:], v)
c.config._allowedPublicKeys[pk] = struct{}{} c.config._allowedPublicKeys[pk] = struct{}{}
} }
return
} }
type SetupOption interface { type SetupOption interface {
@ -33,9 +51,11 @@ type Peer struct {
type NodeInfo map[string]interface{} type NodeInfo map[string]interface{}
type NodeInfoPrivacy bool type NodeInfoPrivacy bool
type AllowedPublicKey ed25519.PublicKey type AllowedPublicKey ed25519.PublicKey
type PeerFilter func(net.IP) bool
func (a ListenAddress) isSetupOption() {} func (a ListenAddress) isSetupOption() {}
func (a Peer) isSetupOption() {} func (a Peer) isSetupOption() {}
func (a NodeInfo) isSetupOption() {} func (a NodeInfo) isSetupOption() {}
func (a NodeInfoPrivacy) isSetupOption() {} func (a NodeInfoPrivacy) isSetupOption() {}
func (a AllowedPublicKey) isSetupOption() {} func (a AllowedPublicKey) isSetupOption() {}
func (a PeerFilter) isSetupOption() {}

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

17
src/core/pool.go Normal file
View file

@ -0,0 +1,17 @@
package core
import "sync"
var bytePool = sync.Pool{New: func() interface{} { return []byte(nil) }}
func allocBytes(size int) []byte {
bs := bytePool.Get().([]byte)
if cap(bs) < size {
bs = make([]byte, size)
}
return bs[:size]
}
func freeBytes(bs []byte) {
bytePool.Put(bs[:0]) //nolint:staticcheck
}

View file

@ -21,8 +21,8 @@ const (
typeDebugGetSelfResponse typeDebugGetSelfResponse
typeDebugGetPeersRequest typeDebugGetPeersRequest
typeDebugGetPeersResponse typeDebugGetPeersResponse
typeDebugGetDHTRequest typeDebugGetTreeRequest
typeDebugGetDHTResponse typeDebugGetTreeResponse
) )
type reqInfo struct { type reqInfo struct {
@ -40,7 +40,7 @@ type protoHandler struct {
selfRequests map[keyArray]*reqInfo selfRequests map[keyArray]*reqInfo
peersRequests map[keyArray]*reqInfo peersRequests map[keyArray]*reqInfo
dhtRequests map[keyArray]*reqInfo treeRequests map[keyArray]*reqInfo
} }
func (p *protoHandler) init(core *Core) { func (p *protoHandler) init(core *Core) {
@ -49,7 +49,7 @@ func (p *protoHandler) init(core *Core) {
p.selfRequests = make(map[keyArray]*reqInfo) p.selfRequests = make(map[keyArray]*reqInfo)
p.peersRequests = make(map[keyArray]*reqInfo) p.peersRequests = make(map[keyArray]*reqInfo)
p.dhtRequests = make(map[keyArray]*reqInfo) p.treeRequests = make(map[keyArray]*reqInfo)
} }
// Common functions // Common functions
@ -89,10 +89,10 @@ func (p *protoHandler) _handleDebug(key keyArray, bs []byte) {
p._handleGetPeersRequest(key) p._handleGetPeersRequest(key)
case typeDebugGetPeersResponse: case typeDebugGetPeersResponse:
p._handleGetPeersResponse(key, bs[1:]) p._handleGetPeersResponse(key, bs[1:])
case typeDebugGetDHTRequest: case typeDebugGetTreeRequest:
p._handleGetDHTRequest(key) p._handleGetTreeRequest(key)
case typeDebugGetDHTResponse: case typeDebugGetTreeResponse:
p._handleGetDHTResponse(key, bs[1:]) p._handleGetTreeResponse(key, bs[1:])
} }
} }
@ -126,8 +126,8 @@ func (p *protoHandler) sendGetSelfRequest(key keyArray, callback func([]byte)) {
func (p *protoHandler) _handleGetSelfRequest(key keyArray) { func (p *protoHandler) _handleGetSelfRequest(key keyArray) {
self := p.core.GetSelf() self := p.core.GetSelf()
res := map[string]string{ res := map[string]string{
"key": hex.EncodeToString(self.Key[:]), "key": hex.EncodeToString(self.Key[:]),
"coords": fmt.Sprintf("%v", self.Coords), "routing_entries": fmt.Sprintf("%v", self.RoutingEntries),
} }
bs, err := json.Marshal(res) // FIXME this puts keys in base64, not hex bs, err := json.Marshal(res) // FIXME this puts keys in base64, not hex
if err != nil { if err != nil {
@ -188,47 +188,47 @@ func (p *protoHandler) _handleGetPeersResponse(key keyArray, bs []byte) {
} }
} }
// Get DHT // Get Tree
func (p *protoHandler) sendGetDHTRequest(key keyArray, callback func([]byte)) { func (p *protoHandler) sendGetTreeRequest(key keyArray, callback func([]byte)) {
p.Act(nil, func() { p.Act(nil, func() {
if info := p.dhtRequests[key]; info != nil { if info := p.treeRequests[key]; info != nil {
info.timer.Stop() info.timer.Stop()
delete(p.dhtRequests, key) delete(p.treeRequests, key)
} }
info := new(reqInfo) info := new(reqInfo)
info.callback = callback info.callback = callback
info.timer = time.AfterFunc(time.Minute, func() { info.timer = time.AfterFunc(time.Minute, func() {
p.Act(nil, func() { p.Act(nil, func() {
if p.dhtRequests[key] == info { if p.treeRequests[key] == info {
delete(p.dhtRequests, key) delete(p.treeRequests, key)
} }
}) })
}) })
p.dhtRequests[key] = info p.treeRequests[key] = info
p._sendDebug(key, typeDebugGetDHTRequest, nil) p._sendDebug(key, typeDebugGetTreeRequest, nil)
}) })
} }
func (p *protoHandler) _handleGetDHTRequest(key keyArray) { func (p *protoHandler) _handleGetTreeRequest(key keyArray) {
dinfos := p.core.GetDHT() dinfos := p.core.GetTree()
var bs []byte var bs []byte
for _, dinfo := range dinfos { for _, dinfo := range dinfos {
tmp := append(bs, dinfo.Key[:]...) tmp := append(bs, dinfo.Key[:]...)
const responseOverhead = 2 // 1 debug type, 1 getdht type const responseOverhead = 2 // 1 debug type, 1 gettree type
if uint64(len(tmp))+responseOverhead > p.core.MTU() { if uint64(len(tmp))+responseOverhead > p.core.MTU() {
break break
} }
bs = tmp bs = tmp
} }
p._sendDebug(key, typeDebugGetDHTResponse, bs) p._sendDebug(key, typeDebugGetTreeResponse, bs)
} }
func (p *protoHandler) _handleGetDHTResponse(key keyArray, bs []byte) { func (p *protoHandler) _handleGetTreeResponse(key keyArray, bs []byte) {
if info := p.dhtRequests[key]; info != nil { if info := p.treeRequests[key]; info != nil {
info.timer.Stop() info.timer.Stop()
info.callback(bs) info.callback(bs)
delete(p.dhtRequests, key) delete(p.treeRequests, key)
} }
} }
@ -251,15 +251,16 @@ func (p *protoHandler) getSelfHandler(in json.RawMessage) (interface{}, error) {
if kbs, err = hex.DecodeString(req.Key); err != nil { if kbs, err = hex.DecodeString(req.Key); err != nil {
return nil, err return nil, err
} }
if len(kbs) != ed25519.PublicKeySize {
return nil, fmt.Errorf("invalid public key length")
}
copy(key[:], kbs) copy(key[:], kbs)
ch := make(chan []byte, 1) ch := make(chan []byte, 1)
p.sendGetSelfRequest(key, func(info []byte) { p.sendGetSelfRequest(key, func(info []byte) {
ch <- info ch <- info
}) })
timer := time.NewTimer(6 * time.Second)
defer timer.Stop()
select { select {
case <-timer.C: case <-time.After(6 * time.Second):
return nil, errors.New("timeout") return nil, errors.New("timeout")
case info := <-ch: case info := <-ch:
var msg json.RawMessage var msg json.RawMessage
@ -291,15 +292,16 @@ func (p *protoHandler) getPeersHandler(in json.RawMessage) (interface{}, error)
if kbs, err = hex.DecodeString(req.Key); err != nil { if kbs, err = hex.DecodeString(req.Key); err != nil {
return nil, err return nil, err
} }
if len(kbs) != ed25519.PublicKeySize {
return nil, fmt.Errorf("invalid public key length")
}
copy(key[:], kbs) copy(key[:], kbs)
ch := make(chan []byte, 1) ch := make(chan []byte, 1)
p.sendGetPeersRequest(key, func(info []byte) { p.sendGetPeersRequest(key, func(info []byte) {
ch <- info ch <- info
}) })
timer := time.NewTimer(6 * time.Second)
defer timer.Stop()
select { select {
case <-timer.C: case <-time.After(6 * time.Second):
return nil, errors.New("timeout") return nil, errors.New("timeout")
case info := <-ch: case info := <-ch:
ks := make(map[string][]string) ks := make(map[string][]string)
@ -322,16 +324,16 @@ func (p *protoHandler) getPeersHandler(in json.RawMessage) (interface{}, error)
} }
} }
// Admin socket stuff for "Get DHT" // Admin socket stuff for "Get Tree"
type DebugGetDHTRequest struct { type DebugGetTreeRequest struct {
Key string `json:"key"` Key string `json:"key"`
} }
type DebugGetDHTResponse map[string]interface{} type DebugGetTreeResponse map[string]interface{}
func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) { func (p *protoHandler) getTreeHandler(in json.RawMessage) (interface{}, error) {
var req DebugGetDHTRequest var req DebugGetTreeRequest
if err := json.Unmarshal(in, &req); err != nil { if err := json.Unmarshal(in, &req); err != nil {
return nil, err return nil, err
} }
@ -341,15 +343,16 @@ func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) {
if kbs, err = hex.DecodeString(req.Key); err != nil { if kbs, err = hex.DecodeString(req.Key); err != nil {
return nil, err return nil, err
} }
if len(kbs) != ed25519.PublicKeySize {
return nil, fmt.Errorf("invalid public key length")
}
copy(key[:], kbs) copy(key[:], kbs)
ch := make(chan []byte, 1) ch := make(chan []byte, 1)
p.sendGetDHTRequest(key, func(info []byte) { p.sendGetTreeRequest(key, func(info []byte) {
ch <- info ch <- info
}) })
timer := time.NewTimer(6 * time.Second)
defer timer.Stop()
select { select {
case <-timer.C: case <-time.After(6 * time.Second):
return nil, errors.New("timeout") return nil, errors.New("timeout")
case info := <-ch: case info := <-ch:
ks := make(map[string][]string) ks := make(map[string][]string)
@ -367,7 +370,7 @@ func (p *protoHandler) getDHTHandler(in json.RawMessage) (interface{}, error) {
return nil, err return nil, err
} }
ip := net.IP(address.AddrForKey(kbs)[:]) ip := net.IP(address.AddrForKey(kbs)[:])
res := DebugGetDHTResponse{ip.String(): msg} res := DebugGetTreeResponse{ip.String(): msg}
return res, nil return res, nil
} }
} }

29
src/core/tls.go Normal file
View file

@ -0,0 +1,29 @@
package core
import (
"crypto/tls"
"crypto/x509"
)
func (c *Core) generateTLSConfig(cert *tls.Certificate) (*tls.Config, error) {
config := &tls.Config{
Certificates: []tls.Certificate{*cert},
ClientAuth: tls.NoClientCert,
GetClientCertificate: func(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) {
return cert, nil
},
VerifyPeerCertificate: c.verifyTLSCertificate,
VerifyConnection: c.verifyTLSConnection,
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS13,
}
return config, nil
}
func (c *Core) verifyTLSCertificate(_ [][]byte, _ [][]*x509.Certificate) error {
return nil
}
func (c *Core) verifyTLSConnection(_ tls.ConnectionState) error {
return nil
}

View file

@ -4,65 +4,166 @@ package core
// Used in the initial connection setup and key exchange // Used in the initial connection setup and key exchange
// Some of this could arguably go in wire.go instead // Some of this could arguably go in wire.go instead
import "crypto/ed25519" import (
"bytes"
"crypto/ed25519"
"encoding/binary"
"io"
"golang.org/x/crypto/blake2b"
)
// This is the version-specific metadata exchanged at the start of a connection. // This is the version-specific metadata exchanged at the start of a connection.
// It must always begin with the 4 bytes "meta" and a wire formatted uint64 major version number. // It must always begin with the 4 bytes "meta" and a wire formatted uint64 major version number.
// The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open a connection. // The current version also includes a minor version number, and the box/sig/link keys that need to be exchanged to open a connection.
type version_metadata struct { type version_metadata struct {
meta [4]byte majorVer uint16
ver uint8 // 1 byte in this version minorVer uint16
// Everything after this point potentially depends on the version number, and is subject to change in future versions publicKey ed25519.PublicKey
minorVer uint8 // 1 byte in this version priority uint8
key ed25519.PublicKey
} }
const (
ProtocolVersionMajor uint16 = 0
ProtocolVersionMinor uint16 = 5
)
// Once a major/minor version is released, it is not safe to change any of these
// (including their ordering), it is only safe to add new ones.
const (
metaVersionMajor uint16 = iota // uint16
metaVersionMinor // uint16
metaPublicKey // [32]byte
metaPriority // uint8
)
type handshakeError string
func (e handshakeError) Error() string { return string(e) }
const ErrHandshakeInvalidPreamble = handshakeError("invalid handshake, remote side is not Yggdrasil")
const ErrHandshakeInvalidLength = handshakeError("invalid handshake length, possible version mismatch")
const ErrHandshakeInvalidPassword = handshakeError("invalid password supplied, check your config")
const ErrHandshakeHashFailure = handshakeError("invalid hash length")
const ErrHandshakeIncorrectPassword = handshakeError("password does not match remote side")
// Gets a base metadata with no keys set, but with the correct version numbers. // Gets a base metadata with no keys set, but with the correct version numbers.
func version_getBaseMetadata() version_metadata { func version_getBaseMetadata() version_metadata {
return version_metadata{ return version_metadata{
meta: [4]byte{'m', 'e', 't', 'a'}, majorVer: ProtocolVersionMajor,
ver: 0, minorVer: ProtocolVersionMinor,
minorVer: 4,
} }
} }
// Gets the length of the metadata for this version, used to know how many bytes to read from the start of a connection.
func version_getMetaLength() (mlen int) {
mlen += 4 // meta
mlen++ // ver, as long as it's < 127, which it is in this version
mlen++ // minorVer, as long as it's < 127, which it is in this version
mlen += ed25519.PublicKeySize // key
return
}
// Encodes version metadata into its wire format. // Encodes version metadata into its wire format.
func (m *version_metadata) encode() []byte { func (m *version_metadata) encode(privateKey ed25519.PrivateKey, password []byte) ([]byte, error) {
bs := make([]byte, 0, version_getMetaLength()) bs := make([]byte, 0, 64)
bs = append(bs, m.meta[:]...) bs = append(bs, 'm', 'e', 't', 'a')
bs = append(bs, m.ver) bs = append(bs, 0, 0) // Remaining message length
bs = append(bs, m.minorVer)
bs = append(bs, m.key[:]...) bs = binary.BigEndian.AppendUint16(bs, metaVersionMajor)
if len(bs) != version_getMetaLength() { bs = binary.BigEndian.AppendUint16(bs, 2)
panic("Inconsistent metadata length") bs = binary.BigEndian.AppendUint16(bs, m.majorVer)
bs = binary.BigEndian.AppendUint16(bs, metaVersionMinor)
bs = binary.BigEndian.AppendUint16(bs, 2)
bs = binary.BigEndian.AppendUint16(bs, m.minorVer)
bs = binary.BigEndian.AppendUint16(bs, metaPublicKey)
bs = binary.BigEndian.AppendUint16(bs, ed25519.PublicKeySize)
bs = append(bs, m.publicKey[:]...)
bs = binary.BigEndian.AppendUint16(bs, metaPriority)
bs = binary.BigEndian.AppendUint16(bs, 1)
bs = append(bs, m.priority)
hasher, err := blake2b.New512(password)
if err != nil {
return nil, err
} }
return bs n, err := hasher.Write(m.publicKey)
if err != nil {
return nil, err
}
if n != ed25519.PublicKeySize {
return nil, ErrHandshakeHashFailure
}
hash := hasher.Sum(nil)
bs = append(bs, ed25519.Sign(privateKey, hash)...)
binary.BigEndian.PutUint16(bs[4:6], uint16(len(bs)-6))
return bs, nil
} }
// Decodes version metadata from its wire format into the struct. // Decodes version metadata from its wire format into the struct.
func (m *version_metadata) decode(bs []byte) bool { func (m *version_metadata) decode(r io.Reader, password []byte) error {
if len(bs) != version_getMetaLength() { bh := [6]byte{}
return false if _, err := io.ReadFull(r, bh[:]); err != nil {
return err
} }
offset := 0 meta := [4]byte{'m', 'e', 't', 'a'}
offset += copy(m.meta[:], bs[offset:]) if !bytes.Equal(bh[:4], meta[:]) {
m.ver, offset = bs[offset], offset+1 return ErrHandshakeInvalidPreamble
m.minorVer, offset = bs[offset], offset+1 }
m.key = append([]byte(nil), bs[offset:]...) hl := binary.BigEndian.Uint16(bh[4:6])
return true if hl < ed25519.SignatureSize {
return ErrHandshakeInvalidLength
}
bs := make([]byte, hl)
if _, err := io.ReadFull(r, bs); err != nil {
return err
}
sig := bs[len(bs)-ed25519.SignatureSize:]
bs = bs[:len(bs)-ed25519.SignatureSize]
for len(bs) >= 4 {
op := binary.BigEndian.Uint16(bs[:2])
oplen := binary.BigEndian.Uint16(bs[2:4])
if bs = bs[4:]; len(bs) < int(oplen) {
break
}
switch op {
case metaVersionMajor:
m.majorVer = binary.BigEndian.Uint16(bs[:2])
case metaVersionMinor:
m.minorVer = binary.BigEndian.Uint16(bs[:2])
case metaPublicKey:
m.publicKey = make(ed25519.PublicKey, ed25519.PublicKeySize)
copy(m.publicKey, bs[:ed25519.PublicKeySize])
case metaPriority:
m.priority = bs[0]
}
bs = bs[oplen:]
}
hasher, err := blake2b.New512(password)
if err != nil {
return ErrHandshakeInvalidPassword
}
n, err := hasher.Write(m.publicKey)
if err != nil || n != ed25519.PublicKeySize {
return ErrHandshakeHashFailure
}
hash := hasher.Sum(nil)
if !ed25519.Verify(m.publicKey, hash, sig) {
return ErrHandshakeIncorrectPassword
}
return nil
} }
// Checks that the "meta" bytes and the version numbers are the expected values. // Checks that the "meta" bytes and the version numbers are the expected values.
func (m *version_metadata) check() bool { func (m *version_metadata) check() bool {
base := version_getBaseMetadata() switch {
return base.meta == m.meta && base.ver == m.ver && base.minorVer == m.minorVer case m.majorVer != ProtocolVersionMajor:
return false
case m.minorVer != ProtocolVersionMinor:
return false
case len(m.publicKey) != ed25519.PublicKeySize:
return false
default:
return true
}
} }

78
src/core/version_test.go Normal file
View file

@ -0,0 +1,78 @@
package core
import (
"bytes"
"crypto/ed25519"
"reflect"
"testing"
)
func TestVersionPasswordAuth(t *testing.T) {
for _, tt := range []struct {
password1 []byte // The password on node 1
password2 []byte // The password on node 2
allowed bool // Should the connection have been allowed?
}{
{nil, nil, true}, // Allow: No passwords (both nil)
{nil, []byte(""), true}, // Allow: No passwords (mixed nil and empty string)
{nil, []byte("foo"), false}, // Reject: One node has a password, the other doesn't
{[]byte("foo"), []byte(""), false}, // Reject: One node has a password, the other doesn't
{[]byte("foo"), []byte("foo"), true}, // Allow: Same password
{[]byte("foo"), []byte("bar"), false}, // Reject: Different passwords
} {
pk1, sk1, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatalf("Node 1 failed to generate key: %s", err)
}
metadata1 := &version_metadata{
publicKey: pk1,
}
encoded, err := metadata1.encode(sk1, tt.password1)
if err != nil {
t.Fatalf("Node 1 failed to encode metadata: %s", err)
}
var decoded version_metadata
if allowed := decoded.decode(bytes.NewBuffer(encoded), tt.password2) == nil; allowed != tt.allowed {
t.Fatalf("Permutation %q -> %q should have been %v but was %v", tt.password1, tt.password2, tt.allowed, allowed)
}
}
}
func TestVersionRoundtrip(t *testing.T) {
for _, password := range [][]byte{
nil, []byte(""), []byte("foo"),
} {
for _, test := range []*version_metadata{
{majorVer: 1},
{majorVer: 256},
{majorVer: 2, minorVer: 4},
{majorVer: 2, minorVer: 257},
{majorVer: 258, minorVer: 259},
{majorVer: 3, minorVer: 5, priority: 6},
{majorVer: 260, minorVer: 261, priority: 7},
} {
// Generate a random public key for each time, since it is
// a required field.
pk, sk, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatal(err)
}
test.publicKey = pk
meta, err := test.encode(sk, password)
if err != nil {
t.Fatal(err)
}
encoded := bytes.NewBuffer(meta)
decoded := &version_metadata{}
if err := decoded.decode(encoded, password); err != nil {
t.Fatalf("failed to decode: %s", err)
}
if !reflect.DeepEqual(test, decoded) {
t.Fatalf("round-trip failed\nwant: %+v\n got: %+v", test, decoded)
}
}
}
}

View file

@ -1,60 +0,0 @@
package defaults
import "github.com/yggdrasil-network/yggdrasil-go/src/config"
type MulticastInterfaceConfig = config.MulticastInterfaceConfig
var defaultConfig = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/defaults.defaultConfig=/path/to/config
var defaultAdminListen = "" // LDFLAGS='-X github.com/yggdrasil-network/yggdrasil-go/src/defaults.defaultAdminListen=unix://path/to/sock'
// Defines which parameters are expected by default for configuration on a
// specific platform. These values are populated in the relevant defaults_*.go
// for the platform being targeted. They must be set.
type platformDefaultParameters struct {
// Admin socket
DefaultAdminListen string
// Configuration (used for yggdrasilctl)
DefaultConfigFile string
// Multicast interfaces
DefaultMulticastInterfaces []MulticastInterfaceConfig
// TUN
MaximumIfMTU uint64
DefaultIfMTU uint64
DefaultIfName string
}
func GetDefaults() platformDefaultParameters {
defaults := getDefaults()
if defaultConfig != "" {
defaults.DefaultConfigFile = defaultConfig
}
if defaultAdminListen != "" {
defaults.DefaultAdminListen = defaultAdminListen
}
return defaults
}
// Generates default configuration and returns a pointer to the resulting
// NodeConfig. This is used when outputting the -genconf parameter and also when
// using -autoconf.
func GenerateConfig() *config.NodeConfig {
// Get the defaults for the platform.
defaults := GetDefaults()
// Create a node configuration and populate it.
cfg := new(config.NodeConfig)
cfg.NewKeys()
cfg.Listen = []string{}
cfg.AdminListen = defaults.DefaultAdminListen
cfg.Peers = []string{}
cfg.InterfacePeers = map[string][]string{}
cfg.AllowedPublicKeys = []string{}
cfg.MulticastInterfaces = defaults.DefaultMulticastInterfaces
cfg.IfName = defaults.DefaultIfName
cfg.IfMTU = defaults.DefaultIfMTU
cfg.NodeInfoPrivacy = false
return cfg
}

View file

@ -19,12 +19,14 @@ import (
const keyStoreTimeout = 2 * time.Minute const keyStoreTimeout = 2 * time.Minute
/*
// Out-of-band packet types // Out-of-band packet types
const ( const (
typeKeyDummy = iota // nolint:deadcode,varcheck typeKeyDummy = iota // nolint:deadcode,varcheck
typeKeyLookup typeKeyLookup
typeKeyResponse typeKeyResponse
) )
*/
type keyArray [ed25519.PublicKeySize]byte type keyArray [ed25519.PublicKeySize]byte
@ -57,10 +59,13 @@ func (k *keyStore) init(c *core.Core) {
k.core = c k.core = c
k.address = *address.AddrForKey(k.core.PublicKey()) k.address = *address.AddrForKey(k.core.PublicKey())
k.subnet = *address.SubnetForKey(k.core.PublicKey()) k.subnet = *address.SubnetForKey(k.core.PublicKey())
if err := k.core.SetOutOfBandHandler(k.oobHandler); err != nil { /*if err := k.core.SetOutOfBandHandler(k.oobHandler); err != nil {
err = fmt.Errorf("tun.core.SetOutOfBandHander: %w", err) err = fmt.Errorf("tun.core.SetOutOfBandHander: %w", err)
panic(err) panic(err)
} }*/
k.core.SetPathNotify(func(key ed25519.PublicKey) {
k.update(key)
})
k.keyToInfo = make(map[keyArray]*keyInfo) k.keyToInfo = make(map[keyArray]*keyInfo)
k.addrToInfo = make(map[address.Address]*keyInfo) k.addrToInfo = make(map[address.Address]*keyInfo)
k.addrBuffer = make(map[address.Address]*buffer) k.addrBuffer = make(map[address.Address]*buffer)
@ -177,7 +182,8 @@ func (k *keyStore) resetTimeout(info *keyInfo) {
}) })
} }
func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) { /*
func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) { // nolint:unused
if len(data) != 1+ed25519.SignatureSize { if len(data) != 1+ed25519.SignatureSize {
return return
} }
@ -198,18 +204,26 @@ func (k *keyStore) oobHandler(fromKey, toKey ed25519.PublicKey, data []byte) {
} }
} }
} }
*/
func (k *keyStore) sendKeyLookup(partial ed25519.PublicKey) { func (k *keyStore) sendKeyLookup(partial ed25519.PublicKey) {
sig := ed25519.Sign(k.core.PrivateKey(), partial[:]) /*
bs := append([]byte{typeKeyLookup}, sig...) sig := ed25519.Sign(k.core.PrivateKey(), partial[:])
_ = k.core.SendOutOfBand(partial, bs) bs := append([]byte{typeKeyLookup}, sig...)
//_ = k.core.SendOutOfBand(partial, bs)
_ = bs
*/
k.core.SendLookup(partial)
} }
func (k *keyStore) sendKeyResponse(dest ed25519.PublicKey) { /*
func (k *keyStore) sendKeyResponse(dest ed25519.PublicKey) { // nolint:unused
sig := ed25519.Sign(k.core.PrivateKey(), dest[:]) sig := ed25519.Sign(k.core.PrivateKey(), dest[:])
bs := append([]byte{typeKeyResponse}, sig...) bs := append([]byte{typeKeyResponse}, sig...)
_ = k.core.SendOutOfBand(dest, bs) //_ = k.core.SendOutOfBand(dest, bs)
_ = bs
} }
*/
func (k *keyStore) readPC(p []byte) (int, error) { func (k *keyStore) readPC(p []byte) (int, error) {
buf := make([]byte, k.core.MTU(), 65535) buf := make([]byte, k.core.MTU(), 65535)

View file

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

View file

@ -0,0 +1,39 @@
package multicast
import (
"crypto/ed25519"
"encoding/binary"
"fmt"
)
type multicastAdvertisement struct {
MajorVersion uint16
MinorVersion uint16
PublicKey ed25519.PublicKey
Port uint16
Hash []byte
}
func (m *multicastAdvertisement) MarshalBinary() ([]byte, error) {
b := make([]byte, 0, ed25519.PublicKeySize+8+len(m.Hash))
b = binary.BigEndian.AppendUint16(b, m.MajorVersion)
b = binary.BigEndian.AppendUint16(b, m.MinorVersion)
b = append(b, m.PublicKey...)
b = binary.BigEndian.AppendUint16(b, m.Port)
b = binary.BigEndian.AppendUint16(b, uint16(len(m.Hash)))
b = append(b, m.Hash...)
return b, nil
}
func (m *multicastAdvertisement) UnmarshalBinary(b []byte) error {
if len(b) < ed25519.PublicKeySize+8 {
return fmt.Errorf("invalid multicast beacon")
}
m.MajorVersion = binary.BigEndian.Uint16(b[0:2])
m.MinorVersion = binary.BigEndian.Uint16(b[2:4])
m.PublicKey = append(m.PublicKey[:0], b[4:4+ed25519.PublicKeySize]...)
m.Port = binary.BigEndian.Uint16(b[4+ed25519.PublicKeySize : 6+ed25519.PublicKeySize])
dl := binary.BigEndian.Uint16(b[6+ed25519.PublicKeySize : 8+ed25519.PublicKeySize])
m.Hash = append(m.Hash[:0], b[8+ed25519.PublicKeySize:8+ed25519.PublicKeySize+dl]...)
return nil
}

View file

@ -0,0 +1,38 @@
package multicast
import (
"crypto/ed25519"
"reflect"
"testing"
)
func TestMulticastAdvertisementRoundTrip(t *testing.T) {
pk, sk, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatal(err)
}
orig := multicastAdvertisement{
MajorVersion: 1,
MinorVersion: 2,
PublicKey: pk,
Port: 3,
Hash: sk, // any bytes will do
}
ob, err := orig.MarshalBinary()
if err != nil {
t.Fatal(err)
}
var new multicastAdvertisement
if err := new.UnmarshalBinary(ob); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(orig, new) {
t.Logf("original: %+v", orig)
t.Logf("new: %+v", new)
t.Fatalf("differences found after round-trip")
}
}

View file

@ -4,17 +4,20 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/ed25519" "crypto/ed25519"
"encoding/binary"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"math/rand"
"net" "net"
"net/url" "net/url"
"sync/atomic"
"time" "time"
"github.com/Arceliar/phony" "github.com/Arceliar/phony"
"github.com/gologme/log" "github.com/gologme/log"
"github.com/wlynxg/anet"
"github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/core"
"golang.org/x/crypto/blake2b"
"golang.org/x/net/ipv6" "golang.org/x/net/ipv6"
) )
@ -27,7 +30,7 @@ type Multicast struct {
core *core.Core core *core.Core
log *log.Logger log *log.Logger
sock *ipv6.PacketConn sock *ipv6.PacketConn
_isOpen bool running atomic.Bool
_listeners map[string]*listenerInfo _listeners map[string]*listenerInfo
_interfaces map[string]*interfaceInfo _interfaces map[string]*interfaceInfo
_timer *time.Timer _timer *time.Timer
@ -44,6 +47,8 @@ type interfaceInfo struct {
listen bool listen bool
port uint16 port uint16
priority uint8 priority uint8
password []byte
hash []byte
} }
type listenerInfo struct { type listenerInfo struct {
@ -76,7 +81,7 @@ func New(core *core.Core, log *log.Logger, opts ...SetupOption) (*Multicast, err
} }
func (m *Multicast) _start() error { func (m *Multicast) _start() error {
if m._isOpen { if !m.running.CompareAndSwap(false, true) {
return fmt.Errorf("multicast module is already started") return fmt.Errorf("multicast module is already started")
} }
var anyEnabled bool var anyEnabled bool
@ -84,12 +89,14 @@ func (m *Multicast) _start() error {
anyEnabled = anyEnabled || intf.Beacon || intf.Listen anyEnabled = anyEnabled || intf.Beacon || intf.Listen
} }
if !anyEnabled { if !anyEnabled {
m.running.Store(false)
return nil return nil
} }
m.log.Debugln("Starting multicast module") m.log.Debugln("Starting multicast module")
defer m.log.Debugln("Started multicast module") defer m.log.Debugln("Started multicast module")
addr, err := net.ResolveUDPAddr("udp", string(m.config._groupAddr)) addr, err := net.ResolveUDPAddr("udp", string(m.config._groupAddr))
if err != nil { if err != nil {
m.running.Store(false)
return err return err
} }
listenString := fmt.Sprintf("[::]:%v", addr.Port) listenString := fmt.Sprintf("[::]:%v", addr.Port)
@ -98,6 +105,7 @@ func (m *Multicast) _start() error {
} }
conn, err := lc.ListenPacket(context.Background(), "udp6", listenString) conn, err := lc.ListenPacket(context.Background(), "udp6", listenString)
if err != nil { if err != nil {
m.running.Store(false)
return err return err
} }
m.sock = ipv6.NewPacketConn(conn) m.sock = ipv6.NewPacketConn(conn)
@ -105,7 +113,6 @@ func (m *Multicast) _start() error {
// Windows can't set this flag, so we need to handle it in other ways // Windows can't set this flag, so we need to handle it in other ways
} }
m._isOpen = true
go m.listen() go m.listen()
m.Act(nil, m._multicastStarted) m.Act(nil, m._multicastStarted)
m.Act(nil, m._announce) m.Act(nil, m._announce)
@ -115,11 +122,7 @@ func (m *Multicast) _start() error {
// IsStarted returns true if the module has been started. // IsStarted returns true if the module has been started.
func (m *Multicast) IsStarted() bool { func (m *Multicast) IsStarted() bool {
var isOpen bool return m.running.Load()
phony.Block(m, func() {
isOpen = m._isOpen
})
return isOpen
} }
// Stop stops the multicast module. // Stop stops the multicast module.
@ -133,8 +136,10 @@ func (m *Multicast) Stop() error {
} }
func (m *Multicast) _stop() error { func (m *Multicast) _stop() error {
if !m.running.CompareAndSwap(true, false) {
return nil
}
m.log.Infoln("Stopping multicast module") m.log.Infoln("Stopping multicast module")
m._isOpen = false
if m.sock != nil { if m.sock != nil {
m.sock.Close() m.sock.Close()
} }
@ -144,14 +149,22 @@ func (m *Multicast) _stop() error {
func (m *Multicast) _updateInterfaces() { func (m *Multicast) _updateInterfaces() {
interfaces := m._getAllowedInterfaces() interfaces := m._getAllowedInterfaces()
for name, info := range interfaces { for name, info := range interfaces {
addrs, err := info.iface.Addrs() // 'anet' package is used here to avoid https://github.com/golang/go/issues/40569
addrs, err := anet.InterfaceAddrsByInterface(&info.iface)
if err != nil { if err != nil {
m.log.Warnf("Failed up get addresses for interface %s: %s", name, err) m.log.Warnf("Failed up get addresses for interface %s: %s", name, err)
delete(interfaces, name) delete(interfaces, name)
continue continue
} }
info.addrs = addrs for _, addr := range addrs {
addrIP, _, err := net.ParseCIDR(addr.String())
if err != nil || addrIP.To4() != nil || !addrIP.IsLinkLocalUnicast() {
continue
}
info.addrs = append(info.addrs, addr)
}
interfaces[name] = info interfaces[name] = info
m.log.Debugf("Discovered addresses for interface %s: %s", name, addrs)
} }
m._interfaces = interfaces m._interfaces = interfaces
} }
@ -170,17 +183,21 @@ func (m *Multicast) Interfaces() map[string]net.Interface {
func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo { func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo {
interfaces := make(map[string]*interfaceInfo) interfaces := make(map[string]*interfaceInfo)
// Ask the system for network interfaces // Ask the system for network interfaces
allifaces, err := net.Interfaces() // 'anet' package is used here to avoid https://github.com/golang/go/issues/40569
allifaces, err := anet.Interfaces()
if err != nil { if err != nil {
// Don't panic, since this may be from e.g. too many open files (from too much connection spam) // Don't panic, since this may be from e.g. too many open files (from too much connection spam)
// TODO? log something m.log.Debugf("Failed to get interfaces: %s", err)
return nil return nil
} }
// Work out which interfaces to announce on // Work out which interfaces to announce on
pk := m.core.PublicKey()
for _, iface := range allifaces { for _, iface := range allifaces {
switch { switch {
case iface.Flags&net.FlagUp == 0: case iface.Flags&net.FlagUp == 0:
continue // Ignore interfaces that are down continue // Ignore interfaces that are down
case iface.Flags&net.FlagRunning == 0:
continue // Ignore interfaces that are not running
case iface.Flags&net.FlagMulticast == 0: case iface.Flags&net.FlagMulticast == 0:
continue // Ignore non-multicast interfaces continue // Ignore non-multicast interfaces
case iface.Flags&net.FlagPointToPoint != 0: case iface.Flags&net.FlagPointToPoint != 0:
@ -195,12 +212,23 @@ func (m *Multicast) _getAllowedInterfaces() map[string]*interfaceInfo {
if !ifcfg.Regex.MatchString(iface.Name) { if !ifcfg.Regex.MatchString(iface.Name) {
continue continue
} }
hasher, err := blake2b.New512([]byte(ifcfg.Password))
if err != nil {
continue
}
if n, err := hasher.Write(pk); err != nil {
continue
} else if n != ed25519.PublicKeySize {
continue
}
interfaces[iface.Name] = &interfaceInfo{ interfaces[iface.Name] = &interfaceInfo{
iface: iface, iface: iface,
beacon: ifcfg.Beacon, beacon: ifcfg.Beacon,
listen: ifcfg.Listen, listen: ifcfg.Listen,
port: ifcfg.Port, port: ifcfg.Port,
priority: ifcfg.Priority, priority: ifcfg.Priority,
password: []byte(ifcfg.Password),
hash: hasher.Sum(nil),
} }
break break
} }
@ -218,7 +246,7 @@ func (m *Multicast) AnnounceNow() {
} }
func (m *Multicast) _announce() { func (m *Multicast) _announce() {
if !m._isOpen { if !m.running.Load() {
return return
} }
m._updateInterfaces() m._updateInterfaces()
@ -235,7 +263,7 @@ func (m *Multicast) _announce() {
for name, info := range m._listeners { for name, info := range m._listeners {
// Prepare our stop function! // Prepare our stop function!
stop := func() { stop := func() {
info.listener.Close() info.listener.Cancel()
delete(m._listeners, name) delete(m._listeners, name)
m.log.Debugln("No longer multicasting on", name) m.log.Debugln("No longer multicasting on", name)
} }
@ -248,7 +276,7 @@ func (m *Multicast) _announce() {
// It's possible that the link-local listener address has changed so if // It's possible that the link-local listener address has changed so if
// that is the case then we should clean up the interface listener // that is the case then we should clean up the interface listener
found := false found := false
listenaddr, err := net.ResolveTCPAddr("tcp6", info.listener.Listener.Addr().String()) listenaddr, err := net.ResolveTCPAddr("tcp6", info.listener.Addr().String())
if err != nil { if err != nil {
stop() stop()
continue continue
@ -277,13 +305,9 @@ func (m *Multicast) _announce() {
for _, info := range m._interfaces { for _, info := range m._interfaces {
iface := info.iface iface := info.iface
for _, addr := range info.addrs { for _, addr := range info.addrs {
addrIP, _, _ := net.ParseCIDR(addr.String()) addrIP, _, err := net.ParseCIDR(addr.String())
// Ignore IPv4 addresses // Ignore IPv4 addresses or non-link-local addresses
if addrIP.To4() != nil { if err != nil || addrIP.To4() != nil || !addrIP.IsLinkLocalUnicast() {
continue
}
// Ignore non-link-local addresses
if !addrIP.IsLinkLocalUnicast() {
continue continue
} }
if info.listen { if info.listen {
@ -295,14 +319,17 @@ func (m *Multicast) _announce() {
} }
// Try and see if we already have a TCP listener for this interface // Try and see if we already have a TCP listener for this interface
var linfo *listenerInfo var linfo *listenerInfo
if nfo, ok := m._listeners[iface.Name]; !ok || nfo.listener.Listener == nil { if _, ok := m._listeners[iface.Name]; !ok {
// No listener was found - let's create one // No listener was found - let's create one
urlString := fmt.Sprintf("tls://[%s]:%d", addrIP, info.port) v := &url.Values{}
u, err := url.Parse(urlString) v.Add("priority", fmt.Sprintf("%d", info.priority))
if err != nil { v.Add("password", string(info.password))
panic(err) u := &url.URL{
Scheme: "tls",
Host: net.JoinHostPort(addrIP.String(), fmt.Sprintf("%d", info.port)),
RawQuery: v.Encode(),
} }
if li, err := m.core.Listen(u, iface.Name); err == nil { if li, err := m.core.ListenLocal(u, iface.Name); err == nil {
m.log.Debugln("Started multicasting on", iface.Name) m.log.Debugln("Started multicasting on", iface.Name)
// Store the listener so that we can stop it later if needed // Store the listener so that we can stop it later if needed
linfo = &listenerInfo{listener: li, time: time.Now(), port: info.port} linfo = &listenerInfo{listener: li, time: time.Now(), port: info.port}
@ -321,25 +348,31 @@ func (m *Multicast) _announce() {
if time.Since(linfo.time) < linfo.interval { if time.Since(linfo.time) < linfo.interval {
continue continue
} }
// Get the listener details and construct the multicast beacon addr := linfo.listener.Addr().(*net.TCPAddr)
lladdr := linfo.listener.Listener.Addr().String() adv := multicastAdvertisement{
if a, err := net.ResolveTCPAddr("tcp6", lladdr); err == nil { MajorVersion: core.ProtocolVersionMajor,
a.Zone = "" MinorVersion: core.ProtocolVersionMinor,
destAddr.Zone = iface.Name PublicKey: m.core.PublicKey(),
msg := append([]byte(nil), m.core.GetSelf().Key...) Port: uint16(addr.Port),
msg = append(msg, a.IP...) Hash: info.hash,
pbs := make([]byte, 2) }
binary.BigEndian.PutUint16(pbs, uint16(a.Port)) msg, err := adv.MarshalBinary()
msg = append(msg, pbs...) if err != nil {
_, _ = m.sock.WriteTo(msg, nil, destAddr) continue
}
destAddr.Zone = iface.Name
if _, err = m.sock.WriteTo(msg, nil, destAddr); err != nil {
m.log.Warn("Failed to send multicast beacon:", err)
} }
if linfo.interval.Seconds() < 15 { if linfo.interval.Seconds() < 15 {
linfo.interval += time.Second linfo.interval += time.Second
} }
linfo.time = time.Now()
break break
} }
} }
m._timer = time.AfterFunc(time.Second, func() { annInterval := time.Second + time.Microsecond*(time.Duration(rand.Intn(1048576))) // Randomize delay
m._timer = time.AfterFunc(annInterval, func() {
m.Act(nil, m._announce) m.Act(nil, m._announce)
}) })
} }
@ -350,8 +383,12 @@ func (m *Multicast) listen() {
panic(err) panic(err)
} }
bs := make([]byte, 2048) bs := make([]byte, 2048)
hb := make([]byte, 0, blake2b.Size) // Reused to reduce hash allocations
for { for {
nBytes, rcm, fromAddr, err := m.sock.ReadFrom(bs) if !m.running.Load() {
return
}
n, rcm, fromAddr, err := m.sock.ReadFrom(bs)
if err != nil { if err != nil {
if !m.IsStarted() { if !m.IsStarted() {
return return
@ -369,40 +406,45 @@ func (m *Multicast) listen() {
continue continue
} }
} }
if nBytes < ed25519.PublicKeySize { var adv multicastAdvertisement
if err := adv.UnmarshalBinary(bs[:n]); err != nil {
continue continue
} }
var key ed25519.PublicKey switch {
key = append(key, bs[:ed25519.PublicKeySize]...) case adv.MajorVersion != core.ProtocolVersionMajor:
if bytes.Equal(key, m.core.GetSelf().Key) { continue
continue // don't bother trying to peer with self case adv.MinorVersion != core.ProtocolVersionMinor:
} continue
begin := ed25519.PublicKeySize case adv.PublicKey.Equal(m.core.PublicKey()):
end := nBytes - 2
if end <= begin {
continue // malformed address
}
ip := bs[begin:end]
port := binary.BigEndian.Uint16(bs[end:nBytes])
anAddr := net.TCPAddr{IP: ip, Port: int(port)}
addr, err := net.ResolveTCPAddr("tcp6", anAddr.String())
if err != nil {
continue continue
} }
from := fromAddr.(*net.UDPAddr) from := fromAddr.(*net.UDPAddr)
if !from.IP.Equal(addr.IP) { from.Port = int(adv.Port)
continue
}
var interfaces map[string]*interfaceInfo var interfaces map[string]*interfaceInfo
phony.Block(m, func() { phony.Block(m, func() {
interfaces = m._interfaces interfaces = m._interfaces
}) })
if info, ok := interfaces[from.Zone]; ok && info.listen { if info, ok := interfaces[from.Zone]; ok && info.listen {
addr.Zone = "" hasher, err := blake2b.New512(info.password)
pin := fmt.Sprintf("/?key=%s&priority=%d", hex.EncodeToString(key), info.priority)
u, err := url.Parse("tls://" + addr.String() + pin)
if err != nil { if err != nil {
m.log.Debugln("Call from multicast failed, parse error:", addr.String(), err) continue
}
if n, err := hasher.Write(adv.PublicKey); err != nil {
continue
} else if n != ed25519.PublicKeySize {
continue
}
if !bytes.Equal(hasher.Sum(hb[:0]), adv.Hash) {
continue
}
v := &url.Values{}
v.Add("key", hex.EncodeToString(adv.PublicKey))
v.Add("priority", fmt.Sprintf("%d", info.priority))
v.Add("password", string(info.password))
u := &url.URL{
Scheme: "tls",
Host: from.String(),
RawQuery: v.Encode(),
} }
if err := m.core.CallPeer(u, from.Zone); err != nil { if err := m.core.CallPeer(u, from.Zone); err != nil {
m.log.Debugln("Call from multicast failed:", err) m.log.Debugln("Call from multicast failed:", err)

View file

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

View file

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

View file

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

View file

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

View file

@ -8,42 +8,52 @@ package tun
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"net" "net"
"sync"
"github.com/Arceliar/phony" "github.com/Arceliar/phony"
"golang.zx2c4.com/wireguard/tun" wgtun "golang.zx2c4.com/wireguard/tun"
"github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
"github.com/yggdrasil-network/yggdrasil-go/src/core" "github.com/yggdrasil-network/yggdrasil-go/src/core"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults"
"github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc"
) )
type MTU uint16 type MTU uint16
type ReadWriteCloser interface {
io.ReadWriteCloser
Address() address.Address
Subnet() address.Subnet
MaxMTU() uint64
SetMTU(uint64)
}
// TunAdapter represents a running TUN interface and extends the // TunAdapter represents a running TUN interface and extends the
// yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you // yggdrasil.Adapter type. In order to use the TUN adapter with Yggdrasil, you
// should pass this object to the yggdrasil.SetRouterAdapter() function before // should pass this object to the yggdrasil.SetRouterAdapter() function before
// calling yggdrasil.Start(). // calling yggdrasil.Start().
type TunAdapter struct { type TunAdapter struct {
rwc *ipv6rwc.ReadWriteCloser rwc ReadWriteCloser
log core.Logger log core.Logger
addr address.Address addr address.Address
subnet address.Subnet subnet address.Subnet
mtu uint64 mtu uint64
iface tun.Device iface wgtun.Device
phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below
//mutex sync.RWMutex // Protects the below isOpen bool
isOpen bool isEnabled bool // Used by the writer to drop sessionTraffic if not enabled
isEnabled bool // Used by the writer to drop sessionTraffic if not enabled config struct {
config struct { fd int32
name InterfaceName name InterfaceName
mtu InterfaceMTU mtu InterfaceMTU
} }
ch chan []byte
} }
// Gets the maximum supported MTU for the platform based on the defaults in // Gets the maximum supported MTU for the platform based on the defaults in
// defaults.GetDefaults(). // config.GetDefaults().
func getSupportedMTU(mtu uint64) uint64 { func getSupportedMTU(mtu uint64) uint64 {
if mtu < 1280 { if mtu < 1280 {
return 1280 return 1280
@ -72,25 +82,25 @@ func (tun *TunAdapter) MTU() uint64 {
// DefaultName gets the default TUN interface name for your platform. // DefaultName gets the default TUN interface name for your platform.
func DefaultName() string { func DefaultName() string {
return defaults.GetDefaults().DefaultIfName return config.GetDefaults().DefaultIfName
} }
// DefaultMTU gets the default TUN interface MTU for your platform. This can // DefaultMTU gets the default TUN interface MTU for your platform. This can
// be as high as MaximumMTU(), depending on platform, but is never lower than 1280. // be as high as MaximumMTU(), depending on platform, but is never lower than 1280.
func DefaultMTU() uint64 { func DefaultMTU() uint64 {
return defaults.GetDefaults().DefaultIfMTU return config.GetDefaults().DefaultIfMTU
} }
// MaximumMTU returns the maximum supported TUN interface MTU for your // MaximumMTU returns the maximum supported TUN interface MTU for your
// platform. This can be as high as 65535, depending on platform, but is never // platform. This can be as high as 65535, depending on platform, but is never
// lower than 1280. // lower than 1280.
func MaximumMTU() uint64 { func MaximumMTU() uint64 {
return defaults.GetDefaults().MaximumIfMTU return config.GetDefaults().MaximumIfMTU
} }
// Init initialises the TUN module. You must have acquired a Listener from // Init initialises the TUN module. You must have acquired a Listener from
// the Yggdrasil core before this point and it must not be in use elsewhere. // the Yggdrasil core before this point and it must not be in use elsewhere.
func New(rwc *ipv6rwc.ReadWriteCloser, log core.Logger, opts ...SetupOption) (*TunAdapter, error) { func New(rwc ReadWriteCloser, log core.Logger, opts ...SetupOption) (*TunAdapter, error) {
tun := &TunAdapter{ tun := &TunAdapter{
rwc: rwc, rwc: rwc,
log: log, log: log,
@ -107,10 +117,15 @@ func (tun *TunAdapter) _start() error {
} }
tun.addr = tun.rwc.Address() tun.addr = tun.rwc.Address()
tun.subnet = tun.rwc.Subnet() tun.subnet = tun.rwc.Subnet()
addr := fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(address.GetPrefix())-1) prefix := address.GetPrefix()
var addr string
if tun.addr.IsValid() {
addr = fmt.Sprintf("%s/%d", net.IP(tun.addr[:]).String(), 8*len(prefix[:])-1)
}
if tun.config.name == "none" || tun.config.name == "dummy" { if tun.config.name == "none" || tun.config.name == "dummy" {
tun.log.Debugln("Not starting TUN as ifname is none or dummy") tun.log.Debugln("Not starting TUN as ifname is none or dummy")
tun.isEnabled = false tun.isEnabled = false
go tun.queue()
go tun.write() go tun.write()
return nil return nil
} }
@ -118,7 +133,13 @@ func (tun *TunAdapter) _start() error {
if tun.rwc.MaxMTU() < mtu { if tun.rwc.MaxMTU() < mtu {
mtu = tun.rwc.MaxMTU() mtu = tun.rwc.MaxMTU()
} }
if err := tun.setup(string(tun.config.name), addr, mtu); err != nil { var err error
if tun.config.fd > 0 {
err = tun.setupFD(tun.config.fd, addr, mtu)
} else {
err = tun.setup(string(tun.config.name), addr, mtu)
}
if err != nil {
return err return err
} }
if tun.MTU() != mtu { if tun.MTU() != mtu {
@ -127,6 +148,8 @@ func (tun *TunAdapter) _start() error {
tun.rwc.SetMTU(tun.MTU()) tun.rwc.SetMTU(tun.MTU())
tun.isOpen = true tun.isOpen = true
tun.isEnabled = true tun.isEnabled = true
tun.ch = make(chan []byte, tun.iface.BatchSize())
go tun.queue()
go tun.read() go tun.read()
go tun.write() go tun.write()
return nil return nil
@ -160,3 +183,12 @@ func (tun *TunAdapter) _stop() error {
} }
return nil return nil
} }
const bufPoolSize = TUN_OFFSET_BYTES + 65535
var bufPool = sync.Pool{
New: func() any {
b := [bufPoolSize]byte{}
return b[:]
},
}

View file

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

View file

@ -1,10 +1,11 @@
//go:build openbsd || freebsd //go:build freebsd
// +build openbsd freebsd // +build freebsd
package tun package tun
import ( import (
"encoding/binary" "encoding/binary"
"fmt"
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
@ -53,11 +54,6 @@ struct in6_ifreq {
290 }; 290 };
*/ */
type in6_ifreq_mtu struct {
ifr_name [syscall.IFNAMSIZ]byte
ifru_mtu int
}
type in6_ifreq_addr struct { type in6_ifreq_addr struct {
ifr_name [syscall.IFNAMSIZ]byte ifr_name [syscall.IFNAMSIZ]byte
ifru_addr sockaddr_in6 ifru_addr sockaddr_in6
@ -77,7 +73,7 @@ type in6_ifreq_lifetime struct {
func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
iface, err := wgtun.CreateTUN(ifname, int(mtu)) iface, err := wgtun.CreateTUN(ifname, int(mtu))
if err != nil { if err != nil {
panic(err) return fmt.Errorf("failed to create TUN: %w", err)
} }
tun.iface = iface tun.iface = iface
if mtu, err := iface.MTU(); err == nil { if mtu, err := iface.MTU(); err == nil {
@ -85,7 +81,15 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
} else { } else {
tun.mtu = 0 tun.mtu = 0
} }
return tun.setupAddress(addr) if addr != "" {
return tun.setupAddress(addr)
}
return nil
}
// Configures the "utun" adapter from an existing file descriptor.
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
return fmt.Errorf("setup via FD not supported on this platform")
} }
func (tun *TunAdapter) setupAddress(addr string) error { func (tun *TunAdapter) setupAddress(addr string) error {
@ -103,26 +107,6 @@ func (tun *TunAdapter) setupAddress(addr string) error {
tun.log.Infof("Interface IPv6: %s", addr) tun.log.Infof("Interface IPv6: %s", addr)
tun.log.Infof("Interface MTU: %d", tun.mtu) tun.log.Infof("Interface MTU: %d", tun.mtu)
// Create the MTU request
var ir in6_ifreq_mtu
copy(ir.ifr_name[:], tun.Name())
ir.ifru_mtu = int(tun.mtu)
// Set the MTU
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sfd), uintptr(syscall.SIOCSIFMTU), uintptr(unsafe.Pointer(&ir))); errno != 0 {
err = errno
tun.log.Errorf("Error in SIOCSIFMTU: %v", errno)
// Fall back to ifconfig to set the MTU
cmd := exec.Command("ifconfig", tun.Name(), "mtu", string(tun.mtu))
tun.log.Warnf("Using ifconfig as fallback: %v", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
tun.log.Errorf("SIOCSIFMTU fallback failed: %v.", err)
tun.log.Traceln(string(output))
}
}
// Create the address request // Create the address request
// FIXME: I don't work! // FIXME: I don't work!
var ar in6_ifreq_addr var ar in6_ifreq_addr

View file

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

122
src/tun/tun_openbsd.go Normal file
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

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

View file

@ -4,14 +4,16 @@
package tun package tun
import ( import (
"bytes"
"errors" "errors"
"fmt"
"log" "log"
"net" "net/netip"
"time"
"github.com/yggdrasil-network/yggdrasil-go/src/defaults" "github.com/yggdrasil-network/yggdrasil-go/src/config"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"golang.zx2c4.com/wintun"
wgtun "golang.zx2c4.com/wireguard/tun" wgtun "golang.zx2c4.com/wireguard/tun"
"golang.zx2c4.com/wireguard/windows/elevate" "golang.zx2c4.com/wireguard/windows/elevate"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
@ -22,7 +24,7 @@ import (
// Configures the TUN adapter with the correct IPv6 address and MTU. // Configures the TUN adapter with the correct IPv6 address and MTU.
func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error { func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
if ifname == "auto" { if ifname == "auto" {
ifname = defaults.GetDefaults().DefaultIfName ifname = config.GetDefaults().DefaultIfName
} }
return elevate.DoAsSystem(func() error { return elevate.DoAsSystem(func() error {
var err error var err error
@ -31,13 +33,27 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
if guid, err = windows.GUIDFromString("{8f59971a-7872-4aa6-b2eb-061fc4e9d0a7}"); err != nil { if guid, err = windows.GUIDFromString("{8f59971a-7872-4aa6-b2eb-061fc4e9d0a7}"); err != nil {
return err return err
} }
if iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu)); err != nil { iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu))
return err if err != nil {
// Very rare condition, it will purge the old device and create new
tun.log.Printf("Error creating TUN: '%s'", err)
wintun.Uninstall()
time.Sleep(3 * time.Second)
tun.log.Printf("Trying again")
iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu))
if err != nil {
return err
}
} }
tun.log.Printf("Waiting for TUN to come up")
time.Sleep(1 * time.Second)
tun.iface = iface tun.iface = iface
if err = tun.setupAddress(addr); err != nil { if addr != "" {
tun.log.Errorln("Failed to set up TUN address:", err) tun.log.Printf("Setting up address")
return err if err = tun.setupAddress(addr); err != nil {
tun.log.Errorln("Failed to set up TUN address:", err)
return err
}
} }
if err = tun.setupMTU(getSupportedMTU(mtu)); err != nil { if err = tun.setupMTU(getSupportedMTU(mtu)); err != nil {
tun.log.Errorln("Failed to set up TUN MTU:", err) tun.log.Errorln("Failed to set up TUN MTU:", err)
@ -46,10 +62,16 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
if mtu, err := iface.MTU(); err == nil { if mtu, err := iface.MTU(); err == nil {
tun.mtu = uint64(mtu) tun.mtu = uint64(mtu)
} }
tun.log.Printf("TUN is set up successfully")
return nil return nil
}) })
} }
// Configures the "utun" adapter from an existing file descriptor.
func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
return fmt.Errorf("setup via FD not supported on this platform")
}
// Sets the MTU of the TUN adapter. // Sets the MTU of the TUN adapter.
func (tun *TunAdapter) setupMTU(mtu uint64) error { func (tun *TunAdapter) setupMTU(mtu uint64) error {
if tun.iface == nil || tun.Name() == "" { if tun.iface == nil || tun.Name() == "" {
@ -83,13 +105,9 @@ func (tun *TunAdapter) setupAddress(addr string) error {
return errors.New("Can't configure IPv6 address as TUN adapter is not present") return errors.New("Can't configure IPv6 address as TUN adapter is not present")
} }
if intf, ok := tun.iface.(*wgtun.NativeTun); ok { if intf, ok := tun.iface.(*wgtun.NativeTun); ok {
if ipaddr, ipnet, err := net.ParseCIDR(addr); err == nil { if ipnet, err := netip.ParsePrefix(addr); err == nil {
luid := winipcfg.LUID(intf.LUID()) luid := winipcfg.LUID(intf.LUID())
addresses := append([]net.IPNet{}, net.IPNet{ addresses := []netip.Prefix{ipnet}
IP: ipaddr,
Mask: ipnet.Mask,
})
err := luid.SetIPAddressesForFamily(windows.AF_INET6, addresses) err := luid.SetIPAddressesForFamily(windows.AF_INET6, addresses)
if err == windows.ERROR_OBJECT_ALREADY_EXISTS { if err == windows.ERROR_OBJECT_ALREADY_EXISTS {
cleanupAddressesOnDisconnectedInterfaces(windows.AF_INET6, addresses) cleanupAddressesOnDisconnectedInterfaces(windows.AF_INET6, addresses)
@ -112,24 +130,13 @@ func (tun *TunAdapter) setupAddress(addr string) error {
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved. * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
*/ */
func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []net.IPNet) { func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []netip.Prefix) {
if len(addresses) == 0 { if len(addresses) == 0 {
return return
} }
includedInAddresses := func(a net.IPNet) bool { addrHash := make(map[netip.Addr]bool, len(addresses))
// TODO: this makes the whole algorithm O(n^2). But we can't stick net.IPNet in a Go hashmap. Bummer! for i := range addresses {
for _, addr := range addresses { addrHash[addresses[i].Addr()] = true
ip := addr.IP
if ip4 := ip.To4(); ip4 != nil {
ip = ip4
}
mA, _ := addr.Mask.Size()
mB, _ := a.Mask.Size()
if bytes.Equal(ip, a.IP) && mA == mB {
return true
}
}
return false
} }
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault) interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault)
if err != nil { if err != nil {
@ -140,11 +147,10 @@ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, add
continue continue
} }
for address := iface.FirstUnicastAddress; address != nil; address = address.Next { for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
ip := address.Address.IP() if ip, _ := netip.AddrFromSlice(address.Address.IP()); addrHash[ip] {
ipnet := net.IPNet{IP: ip, Mask: net.CIDRMask(int(address.OnLinkPrefixLength), 8*len(ip))} prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength))
if includedInAddresses(ipnet) { log.Printf("Cleaning up stale address %s from interface %s", prefix.String(), iface.FriendlyName())
log.Printf("Cleaning up stale address %s from interface %s", ipnet.String(), iface.FriendlyName()) iface.LUID.DeleteIPAddress(prefix)
iface.LUID.DeleteIPAddress(ipnet)
} }
} }
} }