diff --git a/go.mod b/go.mod index 69007d64..8307e0b9 100644 --- a/go.mod +++ b/go.mod @@ -21,11 +21,15 @@ require ( github.com/siderolabs/discovery-api v0.1.4 github.com/siderolabs/discovery-client v0.1.9 github.com/spf13/cobra v1.8.1 - github.com/vishvananda/netlink v1.1.0 + github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.26.0 + golang.org/x/sync v0.8.0 golang.org/x/sys v0.24.0 + golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 + google.golang.org/grpc v1.65.0 + google.golang.org/protobuf v1.34.2 ) require ( @@ -127,13 +131,12 @@ require ( golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect golang.org/x/text v0.17.0 // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect golang.org/x/tools v0.24.0 // indirect - golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect gotest.tools/v3 v3.5.1 // indirect + gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect lukechampine.com/blake3 v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 7152e6b2..924d69f2 100644 --- a/go.sum +++ b/go.sum @@ -558,9 +558,9 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 h1:8mhqcHPqTMhSPoslhGYihEgSfc77+7La1P6kiB6+9So= +github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/warpfork/go-testmark v0.12.1 h1:rMgCpJfwy1sJ50x0M0NgyphxYYPMOODIJHhsXyEHU0s= @@ -664,13 +664,14 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -702,6 +703,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -720,6 +723,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= @@ -730,8 +735,6 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs= google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -740,8 +743,6 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -775,6 +776,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ= +gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= diff --git a/internal/cli/cluster.go b/internal/cli/cluster.go index 9cef88e4..8e13aa0a 100644 --- a/internal/cli/cluster.go +++ b/internal/cli/cluster.go @@ -24,6 +24,22 @@ type Cluster struct { config *config.Config } +func (c *Cluster) User() (*User, error) { + userKey := c.config.Clusters[c.Name].UserKey + if userKey == nil { + return nil, errors.New("cluster user_key must be set in the config") + } + return NewUser(userKey) +} + +func (c *Cluster) Machines() []config.MachineConnection { + cfg, ok := c.config.Clusters[c.Name] + if !ok { + return nil + } + return cfg.Machines +} + func (c *Cluster) toConfig() *config.Cluster { return &config.Cluster{ Name: c.Name, @@ -126,17 +142,13 @@ func (c *Cluster) AddMachine(ctx context.Context, name, user, host string, port _ = exec.Close() }() - wgUserKey := c.config.Clusters[c.Name].UserKey - if wgUserKey == nil { - return "", errors.New("cluster user_key must be set in the config") - } - wgUser, err := NewUser(wgUserKey) + clusterUser, err := c.User() if err != nil { - return "", fmt.Errorf("create user from key: %w", err) + return "", err } userPeerCfg := network.PeerConfig{ - Subnet: netip.PrefixFrom(wgUser.Address(), 128), - PublicKey: wgUser.PublicKey(), + Subnet: netip.PrefixFrom(clusterUser.IPv6(), 128), + PublicKey: clusterUser.PublicKey(), } mcfg, err := machine.NewBootstrapConfig(name, netip.Prefix{}, userPeerCfg) if err != nil { diff --git a/internal/cli/config/machine.go b/internal/cli/config/machine.go index c502485d..0d8cdffc 100644 --- a/internal/cli/config/machine.go +++ b/internal/cli/config/machine.go @@ -1,6 +1,8 @@ package config -import "uncloud/internal/secret" +import ( + "uncloud/internal/secret" +) type MachineConnection struct { User string `toml:"user,omitempty"` diff --git a/internal/cli/user.go b/internal/cli/user.go index 24fa7ab9..ed91d899 100644 --- a/internal/cli/user.go +++ b/internal/cli/user.go @@ -4,6 +4,7 @@ import ( "fmt" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "net/netip" + "uncloud/internal/machine/network" "uncloud/internal/secret" ) @@ -43,9 +44,6 @@ func (u *User) PublicKey() secret.Secret { return pubKey[:] } -func (u *User) Address() netip.Addr { - pubKey := u.PublicKey() - bytes := [16]byte{0xfd, 0xcc} - copy(bytes[2:], pubKey[:14]) - return netip.AddrFrom16(bytes) +func (u *User) IPv6() netip.Addr { + return network.PeerIPv6(u.PublicKey()) } diff --git a/internal/machine/network/ip.go b/internal/machine/network/ip.go index dd83eaa6..d3830a49 100644 --- a/internal/machine/network/ip.go +++ b/internal/machine/network/ip.go @@ -3,6 +3,7 @@ package network import ( "net" "net/netip" + "uncloud/internal/secret" ) // MachineIP returns the IP address of the machine which is the first address in the subnet. @@ -10,6 +11,13 @@ func MachineIP(subnet netip.Prefix) netip.Addr { return subnet.Masked().Addr().Next() } +// PeerIPv6 returns the IPv6 address of a peer derived from the first 14 bytes of its public key. +func PeerIPv6(publicKey secret.Secret) netip.Addr { + bytes := [16]byte{0xfd, 0xcc} + copy(bytes[2:], publicKey[:14]) + return netip.AddrFrom16(bytes) +} + func prefixToIPNet(prefix netip.Prefix) net.IPNet { return net.IPNet{ IP: prefix.Addr().AsSlice(), diff --git a/internal/machine/network/wireguard.go b/internal/machine/network/wireguard.go index a5173273..c44a09e7 100644 --- a/internal/machine/network/wireguard.go +++ b/internal/machine/network/wireguard.go @@ -10,7 +10,7 @@ import ( const ( WireGuardInterfaceName = "uncloud" WireGuardPort = 51820 - // WireGuardKeepaliveInterval is sensible interval that works with a wide variety of firewalls is 25 seconds. + // WireGuardKeepaliveInterval is sensible interval that works with a wide variety of firewalls. WireGuardKeepaliveInterval = 25 * time.Second ) diff --git a/internal/machine/network/wireguard_linux.go b/internal/machine/network/wireguard_linux.go index a354e70f..33be241e 100644 --- a/internal/machine/network/wireguard_linux.go +++ b/internal/machine/network/wireguard_linux.go @@ -11,6 +11,7 @@ import ( "golang.zx2c4.com/wireguard/wgctrl" "log/slog" "net/netip" + "slices" "time" ) @@ -101,11 +102,15 @@ func (n *WireGuardNetwork) Configure(config Config) error { } slog.Info("Configured WireGuard interface.", "name", n.link.Attrs().Name) - if err = n.updateSubnet(config.Subnet); err != nil { + machineIP := MachineIP(config.Subnet) + machineIPSubnet := netip.PrefixFrom(machineIP, config.Subnet.Bits()) + machineIPv6 := netip.PrefixFrom(PeerIPv6(config.PublicKey), 128) + addrs := []netip.Prefix{machineIPSubnet, machineIPv6} + if err = n.updateAddresses(addrs); err != nil { return err } - slog.Info("Updated the subnet of the WireGuard interface.", - "name", n.link.Attrs().Name, "subnet", config.Subnet) + slog.Info("Updated addresses of the WireGuard interface.", + "name", n.link.Attrs().Name, "addrs", addrs) // Bring the WireGuard interface up if it's not already up. if n.link.Attrs().Flags&unix.IFF_UP != unix.IFF_UP { @@ -123,27 +128,30 @@ func (n *WireGuardNetwork) Configure(config Config) error { return nil } -// updateSubnet assigns the subnet and the first IP address in it to the WireGuard interface. +// updateAddresses assigns addresses to the WireGuard interface and removes old ones. // It also removes any other addresses that have been added out of band. -func (n *WireGuardNetwork) updateSubnet(subnet netip.Prefix) error { - machineIP := MachineIP(subnet) - ipSubnet := prefixToIPNet(netip.PrefixFrom(machineIP, subnet.Bits())) - if err := netlink.AddrAdd(n.link, &netlink.Addr{IPNet: &ipSubnet}); err != nil { - if !errors.Is(err, unix.EEXIST) { - return fmt.Errorf("add subnet address to WireGuard link %q: %w", n.link.Attrs().Name, err) +func (n *WireGuardNetwork) updateAddresses(addrs []netip.Prefix) error { + for _, addr := range addrs { + ipNet := prefixToIPNet(addr) + if err := netlink.AddrAdd(n.link, &netlink.Addr{IPNet: &ipNet}); err != nil { + if !errors.Is(err, unix.EEXIST) { + return fmt.Errorf("add subnet address to WireGuard link %q: %w", n.link.Attrs().Name, err) + } } } - // Remove the old subnet address if it has changed and remove any other addresses that have been added out of band. + // Remove the old addresses or any other addresses that have been added out of band. linkAddrs, err := netlink.AddrList(n.link, netlink.FAMILY_ALL) if err != nil { return fmt.Errorf("list addresses on WireGuard link %q: %w", n.link.Attrs().Name, err) } - for _, addr := range linkAddrs { - if addr.IPNet.String() == ipSubnet.String() { + for _, linkAddr := range linkAddrs { + if slices.ContainsFunc(addrs, func(a netip.Prefix) bool { + return linkAddr.IPNet.String() == a.String() + }) { continue } - if err = netlink.AddrDel(n.link, &addr); err != nil { - return fmt.Errorf("remove address %q from WireGuard link %q: %w", addr.IPNet, n.link.Attrs().Name, err) + if err = netlink.AddrDel(n.link, &linkAddr); err != nil { + return fmt.Errorf("remove address %q from WireGuard link %q: %w", linkAddr.IPNet, n.link.Attrs().Name, err) } } return nil