Skip to content

Commit f2c863a

Browse files
Merge branch 'develop' into pledge
2 parents a6fcdfc + 83ec58a commit f2c863a

File tree

25 files changed

+562
-340
lines changed

25 files changed

+562
-340
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,31 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2626
- in case of vulnerabilities.
2727
-->
2828

29+
## [0.5.10] - 2024-11-24
30+
31+
### Added
32+
33+
* The `getPeers` admin endpoint will now report the current transmit/receive rate for each given peer
34+
* The `getMulticastInterfaces` admin endpoint now reports much more useful information about each interface, rather than just a list of interface names
35+
36+
### Changed
37+
38+
* Minor tweaks to the routing algorithm:
39+
* The next-hop selection will now prefer shorter paths when the costed distance is otherwise equal, tiebreaking on peering uptime to fall back to more stable paths
40+
* Link cost calculations have been smoothed out, making the costs less sensitive to sudden spikes in latency
41+
* Reusable name lookup and peer connection logic across different peering types for more consistent behaviour
42+
* Some comments in the configuration file have been revised for clarity
43+
* Upgrade dependencies
44+
45+
### Fixed
46+
47+
* Nodes with `IfName` set to `none` will now correctly respond to debug RPC requests
48+
* The admin socket will now be created reliably before dropping privileges with `-user`
49+
* Clear supplementary groups when providing a group ID as well as a user ID to `-user`
50+
* SOCKS and WebSocket peerings should now use the correct source interface when specified in `InterfacePeers`
51+
* `Peers` and `InterfacePeers` addresses that are obviously invalid (such as unspecified or multicast addresses) will now be correctly ignored
52+
* Listeners should now shut down correctly, which should resolve issues where multicast listeners for specific interfaces would not come back up or would log errors
53+
2954
## [0.5.9] - 2024-10-19
3055

3156
### Added

cmd/yggdrasil/chuser_unix.go

Lines changed: 32 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,83 +4,53 @@
44
package main
55

66
import (
7-
"errors"
87
"fmt"
9-
"math"
10-
osuser "os/user"
8+
"os/user"
119
"strconv"
1210
"strings"
13-
"syscall"
11+
12+
"golang.org/x/sys/unix"
1413
)
1514

16-
func chuser(user string) error {
17-
group := ""
18-
if i := strings.IndexByte(user, ':'); i >= 0 {
19-
user, group = user[:i], user[i+1:]
20-
}
15+
func chuser(input string) error {
16+
givenUser, givenGroup, _ := strings.Cut(input, ":")
2117

22-
u := (*osuser.User)(nil)
23-
g := (*osuser.Group)(nil)
18+
var (
19+
err error
20+
usr *user.User
21+
grp *user.Group
22+
uid, gid int
23+
)
2424

25-
if user != "" {
26-
if _, err := strconv.ParseUint(user, 10, 32); err == nil {
27-
u, err = osuser.LookupId(user)
28-
if err != nil {
29-
return fmt.Errorf("failed to lookup user by id %q: %v", user, err)
30-
}
31-
} else {
32-
u, err = osuser.Lookup(user)
33-
if err != nil {
34-
return fmt.Errorf("failed to lookup user by name %q: %v", user, err)
35-
}
25+
if usr, err = user.LookupId(givenUser); err != nil {
26+
if usr, err = user.Lookup(givenUser); err != nil {
27+
return err
3628
}
3729
}
38-
if group != "" {
39-
if _, err := strconv.ParseUint(group, 10, 32); err == nil {
40-
g, err = osuser.LookupGroupId(group)
41-
if err != nil {
42-
return fmt.Errorf("failed to lookup group by id %q: %v", user, err)
43-
}
44-
} else {
45-
g, err = osuser.LookupGroup(group)
46-
if err != nil {
47-
return fmt.Errorf("failed to lookup group by name %q: %v", user, err)
48-
}
49-
}
30+
if uid, err = strconv.Atoi(usr.Uid); err != nil {
31+
return err
5032
}
5133

52-
if g != nil {
53-
gid, _ := strconv.ParseUint(g.Gid, 10, 32)
54-
var err error
55-
if gid < math.MaxInt {
56-
err = syscall.Setgid(int(gid))
57-
} else {
58-
err = errors.New("gid too big")
34+
if givenGroup != "" {
35+
if grp, err = user.LookupGroupId(givenGroup); err != nil {
36+
if grp, err = user.LookupGroup(givenGroup); err != nil {
37+
return err
38+
}
5939
}
6040

61-
if err != nil {
62-
return fmt.Errorf("failed to setgid %d: %v", gid, err)
63-
}
64-
} else if u != nil {
65-
gid, _ := strconv.ParseUint(u.Gid, 10, 32)
66-
err := syscall.Setgid(int(uint32(gid)))
67-
if err != nil {
68-
return fmt.Errorf("failed to setgid %d: %v", gid, err)
69-
}
41+
gid, _ = strconv.Atoi(grp.Gid)
42+
} else {
43+
gid, _ = strconv.Atoi(usr.Gid)
7044
}
7145

72-
if u != nil {
73-
uid, _ := strconv.ParseUint(u.Uid, 10, 32)
74-
var err error
75-
if uid < math.MaxInt {
76-
err = syscall.Setuid(int(uid))
77-
} else {
78-
err = errors.New("uid too big")
79-
}
80-
81-
if err != nil {
82-
return fmt.Errorf("failed to setuid %d: %v", uid, err)
83-
}
46+
if err := unix.Setgroups([]int{gid}); err != nil {
47+
return fmt.Errorf("setgroups: %d: %v", gid, err)
48+
}
49+
if err := unix.Setgid(gid); err != nil {
50+
return fmt.Errorf("setgid: %d: %v", gid, err)
51+
}
52+
if err := unix.Setuid(uid); err != nil {
53+
return fmt.Errorf("setuid: %d: %v", uid, err)
8454
}
8555

8656
return nil

cmd/yggdrasil/chuser_unix_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
2+
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
3+
4+
package main
5+
6+
import (
7+
"testing"
8+
"os/user"
9+
)
10+
11+
// Usernames must not contain a number sign.
12+
func TestEmptyString (t *testing.T) {
13+
if chuser("") == nil {
14+
t.Fatal("the empty string is not a valid user")
15+
}
16+
}
17+
18+
// Either omit delimiter and group, or omit both.
19+
func TestEmptyGroup (t *testing.T) {
20+
if chuser("0:") == nil {
21+
t.Fatal("the empty group is not allowed")
22+
}
23+
}
24+
25+
// Either user only or user and group.
26+
func TestGroupOnly (t *testing.T) {
27+
if chuser(":0") == nil {
28+
t.Fatal("group only is not allowed")
29+
}
30+
}
31+
32+
// Usenames must not contain the number sign.
33+
func TestInvalidUsername (t *testing.T) {
34+
const username = "#user"
35+
if chuser(username) == nil {
36+
t.Fatalf("'%s' is not a valid username", username)
37+
}
38+
}
39+
40+
// User IDs must be non-negative.
41+
func TestInvalidUserid (t *testing.T) {
42+
if chuser("-1") == nil {
43+
t.Fatal("User ID cannot be negative")
44+
}
45+
}
46+
47+
// Change to the current user by ID.
48+
func TestCurrentUserid (t *testing.T) {
49+
usr, err := user.Current()
50+
if err != nil {
51+
t.Fatal(err)
52+
}
53+
54+
if usr.Uid != "0" {
55+
t.Skip("setgroups(2): Only the superuser may set new groups.")
56+
}
57+
58+
if err = chuser(usr.Uid); err != nil {
59+
t.Fatal(err)
60+
}
61+
}
62+
63+
// Change to a common user by name.
64+
func TestCommonUsername (t *testing.T) {
65+
usr, err := user.Current()
66+
if err != nil {
67+
t.Fatal(err)
68+
}
69+
70+
if usr.Uid != "0" {
71+
t.Skip("setgroups(2): Only the superuser may set new groups.")
72+
}
73+
74+
if err := chuser("nobody"); err != nil {
75+
if _, ok := err.(user.UnknownUserError); ok {
76+
t.Skip(err)
77+
}
78+
t.Fatal(err)
79+
}
80+
}

cmd/yggdrasil/main.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"strings"
1515
"syscall"
1616

17+
"suah.dev/protect"
18+
1719
"github.com/gologme/log"
1820
gsyslog "github.com/hashicorp/go-syslog"
1921
"github.com/hjson/hjson-go/v4"
@@ -39,6 +41,20 @@ type node struct {
3941

4042
// The main function is responsible for configuring and starting Yggdrasil.
4143
func main() {
44+
// Not all operations are coverable with pledge(2), so immediately
45+
// limit file system access with unveil(2), effectively preventing
46+
// "proc exec" promises right from the start:
47+
//
48+
// - read arbitrary config file
49+
// - create/write arbitrary log file
50+
// - read/write/chmod/remove admin socket, if at all
51+
if err := protect.Unveil("/", "rwc"); err != nil {
52+
panic(fmt.Sprintf("unveil: / rwc: %v", err))
53+
}
54+
if err := protect.UnveilBlock(); err != nil {
55+
panic(fmt.Sprintf("unveil: %v", err))
56+
}
57+
4258
genconf := flag.Bool("genconf", false, "print a new config to stdout")
4359
useconf := flag.Bool("useconf", false, "read HJSON/JSON config from stdin")
4460
useconffile := flag.String("useconffile", "", "read HJSON/JSON config from specified file path")
@@ -191,9 +207,16 @@ func main() {
191207

192208
// Set up the Yggdrasil node itself.
193209
{
210+
iprange := net.IPNet{
211+
IP: net.ParseIP("200::"),
212+
Mask: net.CIDRMask(7, 128),
213+
}
194214
options := []core.SetupOption{
195215
core.NodeInfo(cfg.NodeInfo),
196216
core.NodeInfoPrivacy(cfg.NodeInfoPrivacy),
217+
core.PeerFilter(func(ip net.IP) bool {
218+
return !iprange.Contains(ip)
219+
}),
197220
}
198221
for _, addr := range cfg.Listen {
199222
options = append(options, core.ListenAddress(addr))

cmd/yggdrasilctl/main.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,9 @@ func run() int {
186186
if err := json.Unmarshal(recv.Response, &resp); err != nil {
187187
panic(err)
188188
}
189-
table.SetHeader([]string{"URI", "State", "Dir", "IP Address", "Uptime", "RTT", "RX", "TX", "Pr", "Cost", "Last Error"})
189+
table.SetHeader([]string{"URI", "State", "Dir", "IP Address", "Uptime", "RTT", "RX", "TX", "Down", "Up", "Pr", "Cost", "Last Error"})
190190
for _, peer := range resp.Peers {
191-
state, lasterr, dir, rtt := "Up", "-", "Out", "-"
191+
state, lasterr, dir, rtt, rxr, txr := "Up", "-", "Out", "-", "-", "-"
192192
if !peer.Up {
193193
state, lasterr = "Down", fmt.Sprintf("%s ago: %s", peer.LastErrorTime.Round(time.Second), peer.LastError)
194194
} else if rttms := float64(peer.Latency.Microseconds()) / 1000; rttms > 0 {
@@ -202,6 +202,12 @@ func run() int {
202202
uri.RawQuery = ""
203203
uristring = uri.String()
204204
}
205+
if peer.RXRate > 0 {
206+
rxr = peer.RXRate.String() + "/s"
207+
}
208+
if peer.TXRate > 0 {
209+
txr = peer.TXRate.String() + "/s"
210+
}
205211
table.Append([]string{
206212
uristring,
207213
state,
@@ -211,6 +217,8 @@ func run() int {
211217
rtt,
212218
peer.RXBytes.String(),
213219
peer.TXBytes.String(),
220+
rxr,
221+
txr,
214222
fmt.Sprintf("%d", peer.Priority),
215223
fmt.Sprintf("%d", peer.Cost),
216224
lasterr,
@@ -285,9 +293,21 @@ func run() int {
285293
if err := json.Unmarshal(recv.Response, &resp); err != nil {
286294
panic(err)
287295
}
288-
table.SetHeader([]string{"Interface"})
296+
fmtBool := func(b bool) string {
297+
if b {
298+
return "Yes"
299+
}
300+
return "-"
301+
}
302+
table.SetHeader([]string{"Name", "Listen Address", "Beacon", "Listen", "Password"})
289303
for _, p := range resp.Interfaces {
290-
table.Append([]string{p})
304+
table.Append([]string{
305+
p.Name,
306+
p.Address,
307+
fmtBool(p.Beacon),
308+
fmtBool(p.Listen),
309+
fmtBool(p.Password),
310+
})
291311
}
292312
table.Render()
293313

contrib/mobile/mobile.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,15 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
5353
}
5454
// Set up the Yggdrasil node itself.
5555
{
56-
options := []core.SetupOption{}
56+
iprange := net.IPNet{
57+
IP: net.ParseIP("200::"),
58+
Mask: net.CIDRMask(7, 128),
59+
}
60+
options := []core.SetupOption{
61+
core.PeerFilter(func(ip net.IP) bool {
62+
return !iprange.Contains(ip)
63+
}),
64+
}
5765
for _, peer := range m.config.Peers {
5866
options = append(options, core.Peer{URI: peer})
5967
}

go.mod

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/yggdrasil-network/yggdrasil-go
33
go 1.21
44

55
require (
6-
github.com/Arceliar/ironwood v0.0.0-20241016082300-f6fb9da97a17
6+
github.com/Arceliar/ironwood v0.0.0-20241210120540-9deb08d9f8f9
77
github.com/Arceliar/phony v0.0.0-20220903101357-530938a4b13d
88
github.com/cheggaaa/pb/v3 v3.1.5
99
github.com/coder/websocket v1.8.12
@@ -14,10 +14,10 @@ require (
1414
github.com/quic-go/quic-go v0.46.0
1515
github.com/vishvananda/netlink v1.3.0
1616
github.com/wlynxg/anet v0.0.5
17-
golang.org/x/crypto v0.28.0
18-
golang.org/x/net v0.30.0
19-
golang.org/x/sys v0.26.0
20-
golang.org/x/text v0.19.0
17+
golang.org/x/crypto v0.29.0
18+
golang.org/x/net v0.31.0
19+
golang.org/x/sys v0.27.0
20+
golang.org/x/text v0.20.0
2121
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
2222
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
2323
golang.zx2c4.com/wireguard/windows v0.5.3
@@ -34,7 +34,7 @@ require (
3434
go.uber.org/mock v0.4.0 // indirect
3535
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
3636
golang.org/x/mod v0.19.0 // indirect
37-
golang.org/x/sync v0.8.0 // indirect
37+
golang.org/x/sync v0.9.0 // indirect
3838
golang.org/x/tools v0.23.0 // indirect
3939
)
4040

0 commit comments

Comments
 (0)