From 602510c17e916eb989c5c56815b41ae273521899 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Fri, 28 Jul 2023 00:02:06 +0200 Subject: [PATCH 01/42] Feat add basic support for IPv6 networks Newly generated networks automatically generate an IPv6 prefix of size 64 within the ULA address range, devices obtain a randomly generated address within this prefix. Currently, this is Linux only and does not yet support all features (routes currently cause an error). --- client/internal/connect.go | 1 + client/internal/engine.go | 17 +- client/internal/peer/status.go | 1 + iface/iface.go | 18 ++ iface/iface_nonandroid.go | 13 +- iface/tun_linux.go | 13 + iface/tun_unix.go | 23 +- iface/wg_configurer_nonandroid.go | 13 +- management/proto/management.pb.go | 403 +++++++++++++++--------------- management/proto/management.proto | 2 + management/server/account.go | 2 +- management/server/grpcserver.go | 9 +- management/server/network.go | 48 +++- management/server/network_test.go | 2 +- management/server/peer.go | 12 + 15 files changed, 363 insertions(+), 214 deletions(-) diff --git a/client/internal/connect.go b/client/internal/connect.go index 79f97e87f3f..e36a38b33b0 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -219,6 +219,7 @@ func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.Pe engineConf := &EngineConfig{ WgIfaceName: config.WgIface, WgAddr: peerConfig.Address, + WgAddr6: peerConfig.Address6, IFaceBlackList: config.IFaceBlackList, DisableIPv6Discovery: config.DisableIPv6Discovery, WgPrivateKey: key, diff --git a/client/internal/engine.go b/client/internal/engine.go index a81fabde6eb..c53973a81f4 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -52,7 +52,8 @@ type EngineConfig struct { WgIfaceName string // WgAddr is a Wireguard local address (Netbird Network IP) - WgAddr string + WgAddr string + WgAddr6 string // WgPrivateKey is a Wireguard private key of our peer (it MUST never leave the machine) WgPrivateKey wgtypes.Key @@ -179,6 +180,7 @@ func (e *Engine) Start() error { wgIFaceName := e.config.WgIfaceName wgAddr := e.config.WgAddr + wgAddr6 := e.config.WgAddr6 myPrivateKey := e.config.WgPrivateKey var err error transportNet, err := e.newStdNet() @@ -186,7 +188,7 @@ func (e *Engine) Start() error { log.Errorf("failed to create pion's stdnet: %s", err) } - e.wgInterface, err = iface.NewWGIFace(wgIFaceName, wgAddr, iface.DefaultMTU, e.mobileDep.TunAdapter, transportNet) + e.wgInterface, err = iface.NewWGIFace(wgIFaceName, wgAddr, wgAddr6, iface.DefaultMTU, e.mobileDep.TunAdapter, transportNet) if err != nil { log.Errorf("failed creating wireguard interface instance %s: [%s]", wgIFaceName, err.Error()) return err @@ -514,6 +516,16 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error { e.config.WgAddr = conf.Address log.Infof("updated peer address from %s to %s", oldAddr, conf.Address) } + if e.wgInterface.Address6() != nil && e.wgInterface.Address6().String() != conf.Address6 { + oldAddr := e.wgInterface.Address6().String() + log.Debugf("updating peer IPv6 address from %s to %s", oldAddr, conf.Address6) + err := e.wgInterface.UpdateAddr6(conf.Address) + if err != nil { + return err + } + e.config.WgAddr6 = conf.Address6 + log.Infof("updated peer IPv6 address from %s to %s", oldAddr, conf.Address6) + } if conf.GetSshConfig() != nil { err := e.updateSSH(conf.GetSshConfig()) @@ -524,6 +536,7 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error { e.statusRecorder.UpdateLocalPeerState(peer.LocalPeerState{ IP: e.config.WgAddr, + IP6: e.config.WgAddr6, PubKey: e.config.WgPrivateKey.PublicKey().String(), KernelInterface: iface.WireGuardModuleIsLoaded(), FQDN: conf.GetFqdn(), diff --git a/client/internal/peer/status.go b/client/internal/peer/status.go index f75991d8572..75a8a1994f7 100644 --- a/client/internal/peer/status.go +++ b/client/internal/peer/status.go @@ -22,6 +22,7 @@ type State struct { // LocalPeerState contains the latest state of the local peer type LocalPeerState struct { IP string + IP6 string PubKey string KernelInterface bool FQDN string diff --git a/iface/iface.go b/iface/iface.go index 55891d047f1..502b13a6ec1 100644 --- a/iface/iface.go +++ b/iface/iface.go @@ -46,6 +46,11 @@ func (w *WGIface) Address() WGAddress { return w.tun.WgAddress() } +// Address6 returns the IPv6 interface address +func (w *WGIface) Address6() *WGAddress { + return w.tun.WgAddress6() +} + // Configure configures a Wireguard interface // The interface must exist before calling this method (e.g. call interface.Create() before) func (w *WGIface) Configure(privateKey string, port int) error { @@ -68,6 +73,19 @@ func (w *WGIface) UpdateAddr(newAddr string) error { return w.tun.UpdateAddr(addr) } +// UpdateAddr6 updates the IPv6 address of the interface +func (w *WGIface) UpdateAddr6(newAddr6 string) error { + w.mu.Lock() + defer w.mu.Unlock() + + addr, err := parseWGAddress(newAddr6) + if err != nil { + return err + } + + return w.tun.UpdateAddr(addr) +} + // UpdatePeer updates existing Wireguard Peer or creates a new one if doesn't exist // Endpoint is optional func (w *WGIface) UpdatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error { diff --git a/iface/iface_nonandroid.go b/iface/iface_nonandroid.go index da4ef13fd59..68ee0660957 100644 --- a/iface/iface_nonandroid.go +++ b/iface/iface_nonandroid.go @@ -10,7 +10,7 @@ import ( ) // NewWGIFace Creates a new WireGuard interface instance -func NewWGIFace(iFaceName string, address string, mtu int, tunAdapter TunAdapter, transportNet transport.Net) (*WGIface, error) { +func NewWGIFace(iFaceName string, address string, address6 string, mtu int, tunAdapter TunAdapter, transportNet transport.Net) (*WGIface, error) { wgIFace := &WGIface{ mu: sync.Mutex{}, } @@ -20,7 +20,16 @@ func NewWGIFace(iFaceName string, address string, mtu int, tunAdapter TunAdapter return wgIFace, err } - wgIFace.tun = newTunDevice(iFaceName, wgAddress, mtu, transportNet) + var wgAddress6 *WGAddress = nil + if address6 != "" { + tmpWgAddress6, err := parseWGAddress(address6) + wgAddress6 = &tmpWgAddress6 + if err != nil { + return wgIFace, err + } + } + + wgIFace.tun = newTunDevice(iFaceName, wgAddress, wgAddress6, mtu, transportNet) wgIFace.configurer = newWGConfigurer(iFaceName) wgIFace.userspaceBind = !WireGuardModuleIsLoaded() diff --git a/iface/tun_linux.go b/iface/tun_linux.go index 93c03436e16..7bc03f080fa 100644 --- a/iface/tun_linux.go +++ b/iface/tun_linux.go @@ -114,6 +114,19 @@ func (c *tunDevice) assignAddr() error { } else if err != nil { return err } + + // Configure the optional additional IPv6 address if available. + if c.address6 != nil { + log.Debugf("adding IPv6 address %s to interface: %s", c.address6.String(), c.name) + addr6, _ := netlink.ParseAddr(c.address6.String()) + err = netlink.AddrAdd(link, addr6) + if os.IsExist(err) { + log.Infof("interface %s already has the address: %s", c.name, c.address.String()) + } else if err != nil { + return err + } + } + // On linux, the link must be brought up err = netlink.LinkSetUp(link) return err diff --git a/iface/tun_unix.go b/iface/tun_unix.go index f923362a464..7a34f27ff09 100644 --- a/iface/tun_unix.go +++ b/iface/tun_unix.go @@ -19,6 +19,7 @@ import ( type tunDevice struct { name string address WGAddress + address6 *WGAddress mtu int netInterface NetInterface iceBind *bind.ICEBind @@ -27,13 +28,14 @@ type tunDevice struct { close chan struct{} } -func newTunDevice(name string, address WGAddress, mtu int, transportNet transport.Net) *tunDevice { +func newTunDevice(name string, address WGAddress, address6 *WGAddress, mtu int, transportNet transport.Net) *tunDevice { return &tunDevice{ - name: name, - address: address, - mtu: mtu, - iceBind: bind.NewICEBind(transportNet), - close: make(chan struct{}), + name: name, + address: address, + address6: address6, + mtu: mtu, + iceBind: bind.NewICEBind(transportNet), + close: make(chan struct{}), } } @@ -42,10 +44,19 @@ func (c *tunDevice) UpdateAddr(address WGAddress) error { return c.assignAddr() } +func (c *tunDevice) UpdateAddr6(address6 *WGAddress) error { + c.address6 = address6 + return c.assignAddr() +} + func (c *tunDevice) WgAddress() WGAddress { return c.address } +func (c *tunDevice) WgAddress6() *WGAddress { + return c.address6 +} + func (c *tunDevice) DeviceName() string { return c.name } diff --git a/iface/wg_configurer_nonandroid.go b/iface/wg_configurer_nonandroid.go index 6749c0966ab..b9daa6536ac 100644 --- a/iface/wg_configurer_nonandroid.go +++ b/iface/wg_configurer_nonandroid.go @@ -5,6 +5,7 @@ package iface import ( "fmt" "net" + "strings" "time" log "github.com/sirupsen/logrus" @@ -45,9 +46,13 @@ func (c *wGConfigurer) configureInterface(privateKey string, port int) error { func (c *wGConfigurer) updatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error { //parse allowed ips - _, ipNet, err := net.ParseCIDR(allowedIps) - if err != nil { - return err + var allowedIpNets []net.IPNet + for _, allowedIp := range strings.Split(allowedIps, ",") { + _, ipNet, err := net.ParseCIDR(allowedIp) + allowedIpNets = append(allowedIpNets, *ipNet) + if err != nil { + return err + } } peerKeyParsed, err := wgtypes.ParseKey(peerKey) @@ -57,7 +62,7 @@ func (c *wGConfigurer) updatePeer(peerKey string, allowedIps string, keepAlive t peer := wgtypes.PeerConfig{ PublicKey: peerKeyParsed, ReplaceAllowedIPs: true, - AllowedIPs: []net.IPNet{*ipNet}, + AllowedIPs: allowedIpNets, PersistentKeepaliveInterval: &keepAlive, PresharedKey: preSharedKey, Endpoint: endpoint, diff --git a/management/proto/management.pb.go b/management/proto/management.pb.go index 45ef49e1f7e..51cec1f2c44 100644 --- a/management/proto/management.pb.go +++ b/management/proto/management.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v3.21.9 +// protoc v4.24.4 // source: management.proto package proto @@ -1058,6 +1058,8 @@ type PeerConfig struct { SshConfig *SSHConfig `protobuf:"bytes,3,opt,name=sshConfig,proto3" json:"sshConfig,omitempty"` // Peer fully qualified domain name Fqdn string `protobuf:"bytes,4,opt,name=fqdn,proto3" json:"fqdn,omitempty"` + // Peer's virtual IPv6 address within the Wiretrustee VPN (a Wireguard address config) + Address6 string `protobuf:"bytes,5,opt,name=address6,proto3" json:"address6,omitempty"` } func (x *PeerConfig) Reset() { @@ -1120,6 +1122,13 @@ func (x *PeerConfig) GetFqdn() string { return "" } +func (x *PeerConfig) GetAddress6() string { + if x != nil { + return x.Address6 + } + return "" +} + // NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections type NetworkMap struct { state protoimpl.MessageState @@ -2318,7 +2327,7 @@ var file_management_proto_rawDesc = []byte{ 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, 0x22, 0x81, 0x01, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, + 0x77, 0x6f, 0x72, 0x64, 0x22, 0x9d, 0x01, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x6e, 0x73, 0x12, @@ -2326,202 +2335,204 @@ var file_management_proto_rawDesc = []byte{ 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0xe2, 0x03, 0x0a, 0x0a, 0x4e, 0x65, 0x74, - 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, - 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, - 0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x2e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x65, 0x65, - 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, - 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, - 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, - 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, - 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x52, 0x6f, 0x75, 0x74, - 0x65, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x2e, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x44, 0x4e, - 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x40, 0x0a, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, - 0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x6f, 0x66, 0x66, - 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x46, 0x69, 0x72, - 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, - 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x46, 0x69, 0x72, 0x65, - 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x66, 0x69, 0x72, - 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, - 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x97, 0x01, - 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, - 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x12, 0x33, - 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, - 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0x49, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, - 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, - 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, - 0x65, 0x79, 0x22, 0x20, 0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, - 0x12, 0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x52, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x16, - 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x4f, - 0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x1e, 0x0a, 0x1c, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5b, 0x0a, 0x15, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, - 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x22, 0xea, 0x02, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, - 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a, - 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, - 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x12, 0x14, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x49, 0x44, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x55, 0x73, 0x65, 0x49, - 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, - 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x18, 0x0a, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, - 0x22, 0xb5, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x65, - 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x65, 0x74, - 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, - 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65, - 0x74, 0x72, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, - 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x22, 0xb4, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x47, 0x0a, 0x10, - 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x52, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, - 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, - 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, - 0x6e, 0x65, 0x52, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x22, - 0x58, 0x0a, 0x0a, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x32, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x74, 0x0a, 0x0c, 0x53, 0x69, 0x6d, - 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x44, 0x61, - 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x22, - 0xb3, 0x01, 0x0a, 0x0f, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x12, 0x38, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, - 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, - 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, - 0x73, 0x12, 0x32, 0x0a, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x36, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x36, 0x22, 0xe2, 0x03, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x4d, 0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x0a, 0x70, + 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, + 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, + 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, + 0x65, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, + 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x33, + 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, + 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x40, 0x0a, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, + 0x65, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, + 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, + 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, + 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, + 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, + 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, + 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x14, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, + 0x65, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x97, 0x01, 0x0a, 0x10, 0x52, 0x65, + 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, + 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c, + 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, + 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, + 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, + 0x71, 0x64, 0x6e, 0x22, 0x49, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x20, + 0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x48, 0x0a, 0x08, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, + 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, + 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x08, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x16, 0x0a, 0x08, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, + 0x10, 0x00, 0x22, 0x1e, 0x0a, 0x1c, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x5b, 0x0a, 0x15, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x42, 0x0a, 0x0e, 0x50, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, + 0xea, 0x02, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, + 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, + 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, + 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, + 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, + 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x53, 0x63, 0x6f, + 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x55, 0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x52, 0x65, 0x64, 0x69, + 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, + 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x22, 0xb5, 0x01, 0x0a, + 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x1e, + 0x0a, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x4e, + 0x65, 0x74, 0x49, 0x44, 0x22, 0xb4, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x47, 0x0a, 0x10, 0x4e, 0x61, 0x6d, 0x65, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, + 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, + 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x0b, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x22, 0x58, 0x0a, 0x0a, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x12, 0x32, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x74, 0x0a, 0x0c, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x43, 0x6c, + 0x61, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x22, 0xb3, 0x01, 0x0a, 0x0f, + 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, + 0x38, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0b, 0x4e, 0x61, + 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x72, 0x69, + 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x50, 0x72, 0x69, 0x6d, + 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, - 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, - 0xf0, 0x02, 0x0a, 0x0c, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x12, 0x40, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x6d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, - 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x06, 0x41, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, - 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, - 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, - 0x55, 0x54, 0x10, 0x01, 0x22, 0x1e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, - 0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, - 0x4f, 0x50, 0x10, 0x01, 0x22, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, - 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, - 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, - 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, - 0x10, 0x04, 0x32, 0xd1, 0x03, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, - 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, - 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, - 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, - 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, - 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, - 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, - 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, - 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, - 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, - 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, - 0x47, 0x65, 0x74, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x53, 0x65, 0x61, + 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x22, 0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, + 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, + 0x16, 0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xf0, 0x02, 0x0a, 0x0c, + 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, + 0x65, 0x72, 0x49, 0x50, 0x12, 0x40, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, + 0x65, 0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x44, 0x69, 0x72, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, + 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x3d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, + 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, + 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, + 0x72, 0x74, 0x22, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, + 0x22, 0x1e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, + 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, + 0x22, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, + 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, + 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, + 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x32, 0xd1, + 0x03, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, + 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, + 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, + 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, + 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, + 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/management/proto/management.proto b/management/proto/management.proto index ae90beaf3d0..79b1254a4d9 100644 --- a/management/proto/management.proto +++ b/management/proto/management.proto @@ -167,6 +167,8 @@ message PeerConfig { SSHConfig sshConfig = 3; // Peer fully qualified domain name string fqdn = 4; + // Peer's virtual IPv6 address within the Wiretrustee VPN (a Wireguard address config) + string address6 = 5; } // NetworkMap represents a network state of the peer with the corresponding configuration parameters to establish peer-to-peer connections diff --git a/management/server/account.go b/management/server/account.go index 30a9bd200dc..d5fe6760640 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -1618,7 +1618,7 @@ func addAllGroup(account *Account) error { func newAccountWithId(accountID, userID, domain string) *Account { log.Debugf("creating new account") - network := NewNetwork() + network := NewNetwork(true) peers := make(map[string]*Peer) users := make(map[string]*User) routes := make(map[string]*route.Route) diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index f32f6347a00..796c95e1c5d 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -415,9 +415,12 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot func toPeerConfig(peer *Peer, network *Network, dnsName string) *proto.PeerConfig { netmask, _ := network.Net.Mask.Size() + netmask6, _ := network.Net6.Mask.Size() + // TODO handle case where there is no IPv6 address fqdn := peer.FQDN(dnsName) return &proto.PeerConfig{ Address: fmt.Sprintf("%s/%d", peer.IP.String(), netmask), // take it from the network + Address6: fmt.Sprintf("%s/%d", peer.IP6.String(), netmask6), SshConfig: &proto.SSHConfig{SshEnabled: peer.SSHEnabled}, Fqdn: fqdn, } @@ -427,9 +430,13 @@ func toRemotePeerConfig(peers []*Peer, dnsName string) []*proto.RemotePeerConfig remotePeers := []*proto.RemotePeerConfig{} for _, rPeer := range peers { fqdn := rPeer.FQDN(dnsName) + allowedIps := []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)} + if rPeer.IP6 != nil { + allowedIps = append(allowedIps, fmt.Sprintf(AllowedIP6sFormat, *rPeer.IP6)) + } remotePeers = append(remotePeers, &proto.RemotePeerConfig{ WgPubKey: rPeer.Key, - AllowedIps: []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)}, + AllowedIps: allowedIps, SshConfig: &proto.SSHConfig{SshPubKey: []byte(rPeer.SSHKey)}, Fqdn: fqdn, }) diff --git a/management/server/network.go b/management/server/network.go index c5b165caeda..3365eca019a 100644 --- a/management/server/network.go +++ b/management/server/network.go @@ -19,9 +19,14 @@ const ( SubnetSize = 16 // NetSize is a global network size 100.64.0.0/10 NetSize = 10 + // Subnet6Size is the size of an IPv6 subnet (in Bytes, not Bits) + Subnet6Size = 8 // AllowedIPsFormat generates Wireguard AllowedIPs format (e.g. 100.64.30.1/32) AllowedIPsFormat = "%s/32" + + // AllowedIP6sFormat generates Wireguard AllowedIPs format (e.g. 2001:db8::dead:beef/128) + AllowedIP6sFormat = "%s/128" ) type NetworkMap struct { @@ -36,6 +41,7 @@ type NetworkMap struct { type Network struct { Identifier string `json:"id"` Net net.IPNet `gorm:"serializer:gob"` + Net6 *net.IPNet Dns string // Serial is an ID that increments by 1 when any change to the network happened (e.g. new peer has been added). // Used to synchronize state to the client apps. @@ -46,7 +52,7 @@ type Network struct { // NewNetwork creates a new Network initializing it with a Serial=0 // It takes a random /16 subnet from 100.64.0.0/10 (64 different subnets) -func NewNetwork() *Network { +func NewNetwork(enableV6 bool) *Network { n := iplib.NewNet4(net.ParseIP("100.64.0.0"), NetSize) sub, _ := n.Subnet(SubnetSize) @@ -55,9 +61,21 @@ func NewNetwork() *Network { r := rand.New(s) intn := r.Intn(len(sub)) + var n6 *net.IPNet = nil + if enableV6 { + addrbuf := make([]byte, 16) + addrbuf[0] = 0xfd + addrbuf[1] = 0x00 + _, _ = r.Read(addrbuf[2:Subnet6Size]) + + n6tmp := iplib.NewNet6(addrbuf, Subnet6Size*8, 0).IPNet + n6 = &n6tmp + } + return &Network{ Identifier: xid.New().String(), Net: sub[intn].IPNet, + Net6: n6, Dns: "", Serial: 0} } @@ -80,6 +98,7 @@ func (n *Network) Copy() *Network { return &Network{ Identifier: n.Identifier, Net: n.Net, + Net6: n.Net6, Dns: n.Dns, Serial: n.Serial, } @@ -109,6 +128,33 @@ func AllocatePeerIP(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) { return ips[intn], nil } +// AllocatePeerIP6 pics an available IPv6 from an net.IPNet. +// This method considers already taken IPs and reuses IPs if there are gaps in takenIps +// E.g. if ipNet=100.30.0.0/16 and takenIps=[100.30.0.1, 100.30.0.4] then the result would be 100.30.0.2 or 100.30.0.3 +func AllocatePeerIP6(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) { + + takenIPMap := make(map[string]struct{}) + takenIPMap[ipNet.IP.String()] = struct{}{} + for _, ip := range takenIps { + takenIPMap[ip.String()] = struct{}{} + } + + maskSize, _ := ipNet.Mask.Size() + + s := rand.NewSource(time.Now().Unix()) + r := rand.New(s) + + // TODO for small subnet sizes, randomly generating values until we don't get a duplicate is inefficient and could + // lead to many loop iterations, using a method similar to IPv4 would be preferable here. + + addrbuf := ipNet.IP.To16() + for duplicate := true; duplicate; _, duplicate = takenIPMap[addrbuf.String()] { + _, _ = r.Read(addrbuf[(maskSize / 8):16]) + } + + return addrbuf, nil +} + // generateIPs generates a list of all possible IPs of the given network excluding IPs specified in the exclusion list func generateIPs(ipNet *net.IPNet, exclusions map[string]struct{}) ([]net.IP, int) { diff --git a/management/server/network_test.go b/management/server/network_test.go index b067c4991dc..da164d5b428 100644 --- a/management/server/network_test.go +++ b/management/server/network_test.go @@ -8,7 +8,7 @@ import ( ) func TestNewNetwork(t *testing.T) { - network := NewNetwork() + network := NewNetwork(false) // generated net should be a subnet of a larger 100.64.0.0/10 net ipNet := net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 192, 0, 0}} diff --git a/management/server/peer.go b/management/server/peer.go index 33c9430fcc3..aea312dfdcd 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -81,6 +81,8 @@ type Peer struct { SetupKey string // IP address of the Peer IP net.IP `gorm:"uniqueIndex:idx_peers_account_id_ip"` + // IPv6 address of the Peer + IP6 *net.IP // Meta is a Peer system meta data Meta PeerSystemMeta `gorm:"embedded;embeddedPrefix:meta_"` // Name is peer's name (machine name) @@ -122,6 +124,7 @@ func (p *Peer) Copy() *Peer { Key: p.Key, SetupKey: p.SetupKey, IP: p.IP, + IP6: p.IP6, Meta: p.Meta, Name: p.Name, DNSLabel: p.DNSLabel, @@ -535,12 +538,21 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* if err != nil { return nil, nil, err } + var nextIp6 *net.IP = nil + if network.Net6 != nil { + nextIp6tmp, err := AllocatePeerIP6(*network.Net6, takenIps) + if err != nil { + return nil, nil, err + } + nextIp6 = &nextIp6tmp + } newPeer := &Peer{ ID: xid.New().String(), Key: peer.Key, SetupKey: upperKey, IP: nextIp, + IP6: nextIp6, Meta: peer.Meta, Name: peer.Meta.Hostname, DNSLabel: newLabel, From b22b16c7fe97d010c72645e1c9714bf71c911e7b Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Sat, 29 Jul 2023 00:47:11 +0200 Subject: [PATCH 02/42] Fix firewall configuration for IPv6 networks --- client/firewall/nftables/manager_linux.go | 47 ++++++------ .../firewall/nftables/manager_linux_test.go | 22 +++++- client/internal/acl/manager.go | 76 +++++++++++++++++-- client/internal/acl/mocks/iface_mapper.go | 16 +++- client/internal/dns/server_test.go | 13 +++- management/proto/management.pb.go | 75 ++++++++++-------- management/proto/management.proto | 1 + management/server/grpcserver.go | 9 ++- management/server/policy.go | 10 +++ 9 files changed, 197 insertions(+), 72 deletions(-) diff --git a/client/firewall/nftables/manager_linux.go b/client/firewall/nftables/manager_linux.go index 6c46048b4b4..d6f61c4ec6b 100644 --- a/client/firewall/nftables/manager_linux.go +++ b/client/firewall/nftables/manager_linux.go @@ -61,6 +61,7 @@ type Manager struct { type iFaceMapper interface { Name() string Address() iface.WGAddress + Address6() *iface.WGAddress } // Create nftables firewall manager @@ -189,11 +190,9 @@ func (m *Manager) AddFiltering( } if proto != "all" { - expressions = append(expressions, &expr.Payload{ - DestRegister: 1, - Base: expr.PayloadBaseNetworkHeader, - Offset: uint32(9), - Len: uint32(1), + expressions = append(expressions, &expr.Meta{ + Key: expr.MetaKeyL4PROTO, + Register: 1, }) var protoData []byte @@ -221,14 +220,13 @@ func (m *Manager) AddFiltering( addrLen := uint32(len(rawIP)) addrOffset := uint32(12) if addrLen == 16 { - addrOffset = 8 + addrOffset = uint32(8) } // change to destination address position if need if direction == fw.RuleDirectionOUT { addrOffset += addrLen } - expressions = append(expressions, &expr.Payload{ DestRegister: 1, @@ -394,10 +392,10 @@ func (m *Manager) chain( } if name == FilterInputChainName { m.filterInputChainIPv6, err = getChain(m.filterInputChainIPv6, nftables.TableFamilyIPv6) - return m.tableIPv4, m.filterInputChainIPv6, err + return m.tableIPv6, m.filterInputChainIPv6, err } m.filterOutputChainIPv6, err = getChain(m.filterOutputChainIPv6, nftables.TableFamilyIPv6) - return m.tableIPv4, m.filterOutputChainIPv6, err + return m.tableIPv6, m.filterOutputChainIPv6, err } // table returns the table for the given family of the IP address @@ -448,7 +446,7 @@ func (m *Manager) createTableIfNotExists( } } - table := m.rConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv4}) + table := m.rConn.AddTable(&nftables.Table{Name: tableName, Family: family}) if err := m.rConn.Flush(); err != nil { return nil, err } @@ -507,9 +505,8 @@ func (m *Manager) createChainIfNotExists( }, } - mask, _ := netip.AddrFromSlice(m.wgIface.Address().Network.Mask) - if m.wgIface.Address().IP.To4() == nil { - ip, _ := netip.AddrFromSlice(m.wgIface.Address().Network.IP.To16()) + if family == nftables.TableFamilyIPv6 && m.wgIface.Address6() != nil { + ip, _ := netip.AddrFromSlice(m.wgIface.Address6().Network.IP.To16()) expressions = append(expressions, &expr.Payload{ DestRegister: 2, @@ -521,8 +518,8 @@ func (m *Manager) createChainIfNotExists( SourceRegister: 2, DestRegister: 2, Len: 16, - Xor: []byte{0x0, 0x0, 0x0, 0x0}, - Mask: mask.Unmap().AsSlice(), + Xor: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Mask: m.wgIface.Address6().Network.Mask, }, &expr.Cmp{ Op: expr.CmpOpNeq, @@ -531,7 +528,13 @@ func (m *Manager) createChainIfNotExists( }, &expr.Verdict{Kind: expr.VerdictAccept}, ) - } else { + + _ = m.rConn.AddRule(&nftables.Rule{ + Table: table, + Chain: chain, + Exprs: expressions, + }) + } else if family == nftables.TableFamilyIPv4 && m.wgIface.Address().IP.To4() != nil { ip, _ := netip.AddrFromSlice(m.wgIface.Address().Network.IP.To4()) expressions = append(expressions, &expr.Payload{ @@ -554,13 +557,13 @@ func (m *Manager) createChainIfNotExists( }, &expr.Verdict{Kind: expr.VerdictAccept}, ) - } - _ = m.rConn.AddRule(&nftables.Rule{ - Table: table, - Chain: chain, - Exprs: expressions, - }) + _ = m.rConn.AddRule(&nftables.Rule{ + Table: table, + Chain: chain, + Exprs: expressions, + }) + } expressions = []expr.Any{ &expr.Meta{Key: ifaceKey, Register: 1}, diff --git a/client/firewall/nftables/manager_linux_test.go b/client/firewall/nftables/manager_linux_test.go index 0a5c499b292..4227ebaf0b7 100644 --- a/client/firewall/nftables/manager_linux_test.go +++ b/client/firewall/nftables/manager_linux_test.go @@ -18,8 +18,9 @@ import ( // iFaceMapper defines subset methods of interface required for manager type iFaceMock struct { - NameFunc func() string - AddressFunc func() iface.WGAddress + NameFunc func() string + AddressFunc func() iface.WGAddress + Address6Func func() *iface.WGAddress } func (i *iFaceMock) Name() string { @@ -36,6 +37,13 @@ func (i *iFaceMock) Address() iface.WGAddress { panic("AddressFunc is not set") } +func (i *iFaceMock) Address6() *iface.WGAddress { + if i.Address6Func != nil { + return i.Address6Func() + } + panic("AddressFunc is not set") +} + func TestNftablesManager(t *testing.T) { mock := &iFaceMock{ NameFunc: func() string { @@ -168,6 +176,16 @@ func TestNFtablesCreatePerformance(t *testing.T) { }, } }, + Address6Func: func() *iface.WGAddress { + v6addr, v6net, _ := net.ParseCIDR("fd00:1234:dead:beef::1/64") + return &iface.WGAddress{ + IP: v6addr, + Network: &net.IPNet{ + IP: v6net.IP, + Mask: v6net.Mask, + }, + } + }, } for _, testMax := range []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} { diff --git a/client/internal/acl/manager.go b/client/internal/acl/manager.go index feaaa7b8b80..20ebdb72ab8 100644 --- a/client/internal/acl/manager.go +++ b/client/internal/acl/manager.go @@ -21,6 +21,7 @@ import ( type IFaceMapper interface { Name() string Address() iface.WGAddress + Address6() *iface.WGAddress IsUserspaceBind() bool SetFilter(iface.PacketFilter) error } @@ -98,6 +99,7 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) { if enableSSH { rules = append(rules, &mgmProto.FirewallRule{ PeerIP: "0.0.0.0", + PeerIP6: "::", Direction: mgmProto.FirewallRule_IN, Action: mgmProto.FirewallRule_ACCEPT, Protocol: mgmProto.FirewallRule_TCP, @@ -112,12 +114,14 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) { rules = append(rules, &mgmProto.FirewallRule{ PeerIP: "0.0.0.0", + PeerIP6: "::", Direction: mgmProto.FirewallRule_IN, Action: mgmProto.FirewallRule_ACCEPT, Protocol: mgmProto.FirewallRule_ALL, }, &mgmProto.FirewallRule{ PeerIP: "0.0.0.0", + PeerIP6: "::", Direction: mgmProto.FirewallRule_OUT, Action: mgmProto.FirewallRule_ACCEPT, Protocol: mgmProto.FirewallRule_ALL, @@ -205,6 +209,15 @@ func (d *DefaultManager) protoRuleToFirewallRule( return "", nil, fmt.Errorf("invalid IP address, skipping firewall rule") } + var ip6 *net.IP = nil + if r.PeerIP6 != "" { + ip6tmp := net.ParseIP(r.PeerIP6) + ip6 = &ip6tmp + if ip6 == nil { + return "", nil, fmt.Errorf("invalid IP address, skipping firewall rule") + } + } + protocol := convertToFirewallProtocol(r.Protocol) if protocol == firewall.ProtocolUnknown { return "", nil, fmt.Errorf("invalid protocol type: %d, skipping firewall rule", r.Protocol) @@ -226,7 +239,7 @@ func (d *DefaultManager) protoRuleToFirewallRule( } } - ruleID := d.getRuleID(ip, protocol, int(r.Direction), port, action, "") + ruleID := d.getRuleID(ip, ip6, protocol, int(r.Direction), port, action, "") if rulesPair, ok := d.rulesPairs[ruleID]; ok { return ruleID, rulesPair, nil } @@ -235,9 +248,9 @@ func (d *DefaultManager) protoRuleToFirewallRule( var err error switch r.Direction { case mgmProto.FirewallRule_IN: - rules, err = d.addInRules(ip, protocol, port, action, ipsetName, "") + rules, err = d.addInRules(ip, ip6, protocol, port, action, ipsetName, "") case mgmProto.FirewallRule_OUT: - rules, err = d.addOutRules(ip, protocol, port, action, ipsetName, "") + rules, err = d.addOutRules(ip, ip6, protocol, port, action, ipsetName, "") default: return "", nil, fmt.Errorf("invalid direction, skipping firewall rule") } @@ -252,6 +265,7 @@ func (d *DefaultManager) protoRuleToFirewallRule( func (d *DefaultManager) addInRules( ip net.IP, + ip6 *net.IP, protocol firewall.Protocol, port *firewall.Port, action firewall.Action, @@ -260,27 +274,47 @@ func (d *DefaultManager) addInRules( ) ([]firewall.Rule, error) { var rules []firewall.Rule rule, err := d.manager.AddFiltering( - ip, protocol, nil, port, firewall.RuleDirectionIN, action, ipsetName, comment) + ip, protocol, nil, port, firewall.RuleDirectionIN, action, ipsetName+"-v4", comment) if err != nil { return nil, fmt.Errorf("failed to add firewall rule: %v", err) } rules = append(rules, rule) + if ip6 != nil { + rule, err := d.manager.AddFiltering( + *ip6, protocol, nil, port, firewall.RuleDirectionIN, action, ipsetName+"-v6", comment) + if err != nil { + return nil, fmt.Errorf("failed to add firewall rule: %v", err) + } + rules = append(rules, rule) + } + if shouldSkipInvertedRule(protocol, port) { return rules, nil } rule, err = d.manager.AddFiltering( - ip, protocol, port, nil, firewall.RuleDirectionOUT, action, ipsetName, comment) + ip, protocol, port, nil, firewall.RuleDirectionOUT, action, ipsetName+"-v4", comment) if err != nil { return nil, fmt.Errorf("failed to add firewall rule: %v", err) } + rules = append(rules, rule) + + if ip6 != nil { + rule, err = d.manager.AddFiltering( + *ip6, protocol, port, nil, firewall.RuleDirectionOUT, action, ipsetName+"-v6", comment) + if err != nil { + return nil, fmt.Errorf("failed to add firewall rule: %v", err) + } + rules = append(rules, rule) + } - return append(rules, rule), nil + return rules, nil } func (d *DefaultManager) addOutRules( ip net.IP, + ip6 *net.IP, protocol firewall.Protocol, port *firewall.Port, action firewall.Action, @@ -295,6 +329,15 @@ func (d *DefaultManager) addOutRules( } rules = append(rules, rule) + if ip6 != nil { + rule, err = d.manager.AddFiltering( + *ip6, protocol, nil, port, firewall.RuleDirectionOUT, action, ipsetName+"-v6", comment) + if err != nil { + return nil, fmt.Errorf("failed to add firewall rule: %v", err) + } + rules = append(rules, rule) + } + if shouldSkipInvertedRule(protocol, port) { return rules, nil } @@ -304,20 +347,35 @@ func (d *DefaultManager) addOutRules( if err != nil { return nil, fmt.Errorf("failed to add firewall rule: %v", err) } + rules = append(rules, rule) - return append(rules, rule), nil + if ip6 != nil { + rule, err = d.manager.AddFiltering( + *ip6, protocol, port, nil, firewall.RuleDirectionIN, action, ipsetName+"-v6", comment) + rules = append(rules, rule) + if err != nil { + return nil, fmt.Errorf("failed to add firewall rule: %v", err) + } + } + + return rules, nil } // getRuleID() returns unique ID for the rule based on its parameters. func (d *DefaultManager) getRuleID( ip net.IP, + ip6 *net.IP, proto firewall.Protocol, direction int, port *firewall.Port, action firewall.Action, comment string, ) string { - idStr := ip.String() + string(proto) + strconv.Itoa(direction) + strconv.Itoa(int(action)) + comment + ip6Str := "" + if ip6 != nil { + ip6Str = ip6.String() + } + idStr := ip.String() + ip6Str + string(proto) + strconv.Itoa(direction) + strconv.Itoa(int(action)) + comment if port != nil { idStr += port.String() } @@ -370,6 +428,7 @@ func (d *DefaultManager) squashAcceptRules( // it means that rules for that protocol was already optimized on the // management side if r.PeerIP == "0.0.0.0" { + // TODO IPv6? squashedRules = append(squashedRules, r) squashedProtocols[r.Protocol] = struct{}{} return @@ -413,6 +472,7 @@ func (d *DefaultManager) squashAcceptRules( // add special rule 0.0.0.0 which allows all IP's in our firewall implementations squashedRules = append(squashedRules, &mgmProto.FirewallRule{ PeerIP: "0.0.0.0", + PeerIP6: "::", Direction: direction, Action: mgmProto.FirewallRule_ACCEPT, Protocol: protocol, diff --git a/client/internal/acl/mocks/iface_mapper.go b/client/internal/acl/mocks/iface_mapper.go index 621b2951364..4496f54be37 100644 --- a/client/internal/acl/mocks/iface_mapper.go +++ b/client/internal/acl/mocks/iface_mapper.go @@ -7,8 +7,8 @@ package mocks import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" iface "github.com/netbirdio/netbird/iface" + gomock "go.uber.org/mock/gomock" ) // MockIFaceMapper is a mock of IFaceMapper interface. @@ -48,6 +48,20 @@ func (mr *MockIFaceMapperMockRecorder) Address() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Address", reflect.TypeOf((*MockIFaceMapper)(nil).Address)) } +// Address6 mocks base method. +func (m *MockIFaceMapper) Address6() *iface.WGAddress { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Address6") + ret0, _ := ret[0].(*iface.WGAddress) + return ret0 +} + +// Address6 indicates an expected call of Address6. +func (mr *MockIFaceMapperMockRecorder) Address6() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Address6", reflect.TypeOf((*MockIFaceMapper)(nil).Address6)) +} + // IsUserspaceBind mocks base method. func (m *MockIFaceMapper) IsUserspaceBind() bool { m.ctrl.T.Helper() diff --git a/client/internal/dns/server_test.go b/client/internal/dns/server_test.go index cd7932d07fa..a1d264cbe64 100644 --- a/client/internal/dns/server_test.go +++ b/client/internal/dns/server_test.go @@ -36,6 +36,13 @@ func (w *mocWGIface) Address() iface.WGAddress { Network: network, } } +func (w *mocWGIface) Address6() *iface.WGAddress { + ip, network, _ := net.ParseCIDR("fd00:1234:dead:beef::/64") + return &iface.WGAddress{ + IP: ip, + Network: network, + } +} func (w *mocWGIface) GetFilter() iface.PacketFilter { return w.filter @@ -254,7 +261,7 @@ func TestUpdateDNSServer(t *testing.T) { if err != nil { t.Fatal(err) } - wgIface, err := iface.NewWGIFace(fmt.Sprintf("utun230%d", n), fmt.Sprintf("100.66.100.%d/32", n+1), iface.DefaultMTU, nil, newNet) + wgIface, err := iface.NewWGIFace(fmt.Sprintf("utun230%d", n), fmt.Sprintf("100.66.100.%d/32", n+1), fmt.Sprintf("fd00:1234:dead:beef::%d/128", n+1), iface.DefaultMTU, nil, newNet) if err != nil { t.Fatal(err) } @@ -331,7 +338,7 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) { return } - wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.1/32", iface.DefaultMTU, nil, newNet) + wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.1/32", "fd00:1234:dead:beef::1/128", iface.DefaultMTU, nil, newNet) if err != nil { t.Errorf("build interface wireguard: %v", err) return @@ -781,7 +788,7 @@ func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) { return nil, err } - wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.2/24", iface.DefaultMTU, nil, newNet) + wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.2/24", "fd00:1234:dead:beef::2/128", iface.DefaultMTU, nil, newNet) if err != nil { t.Fatalf("build interface wireguard: %v", err) return nil, err diff --git a/management/proto/management.pb.go b/management/proto/management.pb.go index 51cec1f2c44..9be1eeeae4a 100644 --- a/management/proto/management.pb.go +++ b/management/proto/management.pb.go @@ -2149,6 +2149,7 @@ type FirewallRule struct { Action FirewallRuleAction `protobuf:"varint,3,opt,name=Action,proto3,enum=management.FirewallRuleAction" json:"Action,omitempty"` Protocol FirewallRuleProtocol `protobuf:"varint,4,opt,name=Protocol,proto3,enum=management.FirewallRuleProtocol" json:"Protocol,omitempty"` Port string `protobuf:"bytes,5,opt,name=Port,proto3" json:"Port,omitempty"` + PeerIP6 string `protobuf:"bytes,6,opt,name=PeerIP6,proto3" json:"PeerIP6,omitempty"` } func (x *FirewallRule) Reset() { @@ -2218,6 +2219,13 @@ func (x *FirewallRule) GetPort() string { return "" } +func (x *FirewallRule) GetPeerIP6() string { + if x != nil { + return x.PeerIP6 + } + return "" +} + var File_management_proto protoreflect.FileDescriptor var file_management_proto_rawDesc = []byte{ @@ -2478,7 +2486,7 @@ var file_management_proto_rawDesc = []byte{ 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xf0, 0x02, 0x0a, 0x0c, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x8a, 0x03, 0x0a, 0x0c, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x12, 0x40, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, @@ -2494,45 +2502,46 @@ var file_management_proto_rawDesc = []byte{ 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, - 0x72, 0x74, 0x22, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, - 0x22, 0x1e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, - 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, - 0x22, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, - 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, - 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, - 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x32, 0xd1, - 0x03, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, + 0x72, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x36, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x36, 0x22, 0x1c, 0x0a, 0x09, + 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, + 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x22, 0x1e, 0x0a, 0x06, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, + 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, 0x22, 0x3c, 0x0a, 0x08, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, + 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, + 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, + 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x32, 0xd1, 0x03, 0x0a, 0x11, 0x4d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, + 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, - 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, - 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, - 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, - 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, - 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, - 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, - 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, + 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, + 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, + 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, + 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/management/proto/management.proto b/management/proto/management.proto index 79b1254a4d9..1d7bcdb69a7 100644 --- a/management/proto/management.proto +++ b/management/proto/management.proto @@ -336,6 +336,7 @@ message FirewallRule { action Action = 3; protocol Protocol = 4; string Port = 5; + string PeerIP6 = 6; enum direction { IN = 0; diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index 796c95e1c5d..b91b2f3748a 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -415,12 +415,15 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot func toPeerConfig(peer *Peer, network *Network, dnsName string) *proto.PeerConfig { netmask, _ := network.Net.Mask.Size() - netmask6, _ := network.Net6.Mask.Size() - // TODO handle case where there is no IPv6 address + address6 := "" + if network.Net6 != nil && peer.IP6 != nil { + netmask6, _ := network.Net6.Mask.Size() + address6 = fmt.Sprintf("%s/%d", peer.IP6.String(), netmask6) + } fqdn := peer.FQDN(dnsName) return &proto.PeerConfig{ Address: fmt.Sprintf("%s/%d", peer.IP.String(), netmask), // take it from the network - Address6: fmt.Sprintf("%s/%d", peer.IP6.String(), netmask6), + Address6: address6, SshConfig: &proto.SSHConfig{SshEnabled: peer.SSHEnabled}, Fqdn: fqdn, } diff --git a/management/server/policy.go b/management/server/policy.go index b7b5b331cc5..1e6c5281c57 100644 --- a/management/server/policy.go +++ b/management/server/policy.go @@ -189,6 +189,8 @@ type FirewallRule struct { // PeerIP of the peer PeerIP string + PeerIP6 string + // Direction of the traffic Direction int @@ -270,8 +272,14 @@ func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*Peer, int), fun peersExists[peer.ID] = struct{}{} } + ip6 := "" + if peer.IP6 != nil { + ip6 = peer.IP6.String() + } + fr := FirewallRule{ PeerIP: peer.IP.String(), + PeerIP6: ip6, Direction: direction, Action: string(rule.Action), Protocol: string(rule.Protocol), @@ -279,6 +287,7 @@ func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*Peer, int), fun if isAll { fr.PeerIP = "0.0.0.0" + fr.PeerIP6 = "::" } ruleID := (rule.ID + fr.PeerIP + strconv.Itoa(direction) + @@ -466,6 +475,7 @@ func toProtocolFirewallRules(update []*FirewallRule) []*proto.FirewallRule { result[i] = &proto.FirewallRule{ PeerIP: update[i].PeerIP, + PeerIP6: update[i].PeerIP6, Direction: direction, Action: action, Protocol: protocol, From eb01b8251bb25a8eca73607da1bb24212c5b9f9d Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Mon, 6 Nov 2023 06:46:25 +0100 Subject: [PATCH 03/42] Fix routing configuration for IPv6 networks --- client/internal/routemanager/client.go | 15 +++++++- .../internal/routemanager/systemops_linux.go | 37 ++++++++++++++++--- .../routemanager/systemops_nonandroid.go | 17 +++++++-- .../routemanager/systemops_nonlinux.go | 3 +- .../routemanager/systemops_windows.go | 2 - iface/iface.go | 2 +- management/server/network.go | 1 + 7 files changed, 62 insertions(+), 15 deletions(-) diff --git a/client/internal/routemanager/client.go b/client/internal/routemanager/client.go index fda7b012f34..52b2ffeac96 100644 --- a/client/internal/routemanager/client.go +++ b/client/internal/routemanager/client.go @@ -108,6 +108,8 @@ func (c *clientNetwork) getBestRouteFromStatuses(routePeerStatuses map[string]ro chosen = r.ID chosenScore = tempScore } + // TODO IPv6 consider IPv6 connectivity for route selection? + // Depends on how we want to handle non-ipv6-compatible clients } if chosen == "" { @@ -176,7 +178,11 @@ func (c *clientNetwork) removeRouteFromPeerAndSystem() error { if err != nil { return err } - err = removeFromRouteTableIfNonSystem(c.network, c.wgInterface.Address().IP.String()) + addr := c.wgInterface.Address().IP.String() + if c.chosenRoute.Network.Addr().Is6() { + addr = c.wgInterface.Address6().IP.String() + } + err = removeFromRouteTableIfNonSystem(c.network, addr) if err != nil { return fmt.Errorf("couldn't remove route %s from system, err: %v", c.network, err) @@ -215,7 +221,12 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem() error { return err } } else { - err = addToRouteTableIfNoExists(c.network, c.wgInterface.Address().IP.String()) + gwAddr := c.wgInterface.Address().IP.String() + if c.network.Addr().Is6() { + gwAddr = c.wgInterface.Address6().IP.String() + } + + err = addToRouteTableIfNoExists(c.network, gwAddr, c.wgInterface.Name()) if err != nil { return fmt.Errorf("route %s couldn't be added for peer %s, err: %v", c.network.String(), c.wgInterface.Address().IP.String(), err) diff --git a/client/internal/routemanager/systemops_linux.go b/client/internal/routemanager/systemops_linux.go index fb2938d55e6..19733522f70 100644 --- a/client/internal/routemanager/systemops_linux.go +++ b/client/internal/routemanager/systemops_linux.go @@ -29,8 +29,9 @@ type routeInfoInMemory struct { } const ipv4ForwardingPath = "/proc/sys/net/ipv4/ip_forward" +const ipv6ForwardingPath = "/proc/sys/net/ipv6/conf/all/forwarding" -func addToRouteTable(prefix netip.Prefix, addr string) error { +func addToRouteTable(prefix netip.Prefix, addr string, devName string) error { _, ipNet, err := net.ParseCIDR(prefix.String()) if err != nil { return err @@ -41,15 +42,30 @@ func addToRouteTable(prefix netip.Prefix, addr string) error { addrMask = "/128" } - ip, _, err := net.ParseCIDR(addr + addrMask) + var ip net.IP = nil + if addr != "" { + parsedIp, _, err := net.ParseCIDR(addr + addrMask) + if err != nil { + return err + } + // for IPv6, setting the local IP as the gateway address results in an "invalid argument" error. + // Therefore, we cannot use it to obtain the interface for the route (that would only be possible in IPv4). + if parsedIp.To4() != nil { + ip = parsedIp + } + } + + // We obtain the route interface using the device name. + linkAlias, err := netlink.LinkByName(devName) if err != nil { return err } route := &netlink.Route{ - Scope: netlink.SCOPE_UNIVERSE, - Dst: ipNet, - Gw: ip, + Scope: netlink.SCOPE_UNIVERSE, + Dst: ipNet, + Gw: ip, + LinkIndex: linkAlias.Attrs().Index, } err = netlink.RouteAdd(route) @@ -126,9 +142,18 @@ func enableIPForwarding() error { // check if it is already enabled // see more: https://github.com/netbirdio/netbird/issues/872 + if len(bytes) == 0 || bytes[0] != 49 { + err = os.WriteFile(ipv4ForwardingPath, []byte("1"), 0644) + if err != nil { + return err + } + } + + // Do the same for IPv6 + bytes, err = os.ReadFile(ipv6ForwardingPath) if len(bytes) > 0 && bytes[0] == 49 { return nil } + return os.WriteFile(ipv6ForwardingPath, []byte("1"), 0644) - return os.WriteFile(ipv4ForwardingPath, []byte("1"), 0644) } diff --git a/client/internal/routemanager/systemops_nonandroid.go b/client/internal/routemanager/systemops_nonandroid.go index 3ddf72686de..f514d577bed 100644 --- a/client/internal/routemanager/systemops_nonandroid.go +++ b/client/internal/routemanager/systemops_nonandroid.go @@ -13,8 +13,19 @@ import ( var errRouteNotFound = fmt.Errorf("route not found") -func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error { - defaultGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0")) +// Adds a route for a prefix to the routing table, if such a route doesn't already exist. +// +// Note: depending on the address family and operating system, one of addr or devName may be ignored, and addr should +// always be the local address of the wireguard interface and not an explicit gateway address. +// addr will then be used by some implementations/operating systems to determine the correct device (for OSes that can +// not use devName directly). +// See the concrete implementations of addToRouteTable to see what exactly is done for each OS. +func addToRouteTableIfNoExists(prefix netip.Prefix, addr string, devName string) error { + defaultRoutePrefix := netip.MustParsePrefix("0.0.0.0/0") + if prefix.Addr().Is6() { + defaultRoutePrefix = netip.MustParsePrefix("::/0") + } + defaultGateway, err := getExistingRIBRouteGateway(defaultRoutePrefix) if err != nil && err != errRouteNotFound { return err } @@ -34,7 +45,7 @@ func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error { return nil } - return addToRouteTable(prefix, addr) + return addToRouteTable(prefix, addr, devName) } func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string) error { diff --git a/client/internal/routemanager/systemops_nonlinux.go b/client/internal/routemanager/systemops_nonlinux.go index 537042099f2..d882309a409 100644 --- a/client/internal/routemanager/systemops_nonlinux.go +++ b/client/internal/routemanager/systemops_nonlinux.go @@ -11,7 +11,8 @@ import ( log "github.com/sirupsen/logrus" ) -func addToRouteTable(prefix netip.Prefix, addr string) error { +func addToRouteTable(prefix netip.Prefix, addr string, devName string) error { + // devName is ignored here, the route interface is automatically determined based on the gateway address. cmd := exec.Command("route", "add", prefix.String(), addr) out, err := cmd.Output() if err != nil { diff --git a/client/internal/routemanager/systemops_windows.go b/client/internal/routemanager/systemops_windows.go index 2233748bfd4..288f63df54a 100644 --- a/client/internal/routemanager/systemops_windows.go +++ b/client/internal/routemanager/systemops_windows.go @@ -6,8 +6,6 @@ package routemanager import ( "net" "net/netip" - - "github.com/yusufpapurcu/wmi" ) type Win32_IP4RouteTable struct { diff --git a/iface/iface.go b/iface/iface.go index 502b13a6ec1..6c03808f216 100644 --- a/iface/iface.go +++ b/iface/iface.go @@ -83,7 +83,7 @@ func (w *WGIface) UpdateAddr6(newAddr6 string) error { return err } - return w.tun.UpdateAddr(addr) + return w.tun.UpdateAddr6(&addr) } // UpdatePeer updates existing Wireguard Peer or creates a new one if doesn't exist diff --git a/management/server/network.go b/management/server/network.go index 3365eca019a..419ca8fee06 100644 --- a/management/server/network.go +++ b/management/server/network.go @@ -131,6 +131,7 @@ func AllocatePeerIP(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) { // AllocatePeerIP6 pics an available IPv6 from an net.IPNet. // This method considers already taken IPs and reuses IPs if there are gaps in takenIps // E.g. if ipNet=100.30.0.0/16 and takenIps=[100.30.0.1, 100.30.0.4] then the result would be 100.30.0.2 or 100.30.0.3 +// TODO docs, and recheck if there might be a duplicate issue here? func AllocatePeerIP6(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) { takenIPMap := make(map[string]struct{}) From e66c2b10ec2215c3f0ee3e5d492faa69eb54e798 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Wed, 29 Nov 2023 02:08:33 +0100 Subject: [PATCH 04/42] Feat provide info on IPv6 support for specific client to mgmt server --- client/firewall/iptables/manager_linux.go | 7 +- client/firewall/uspfilter/uspfilter.go | 1 + client/system/info.go | 1 + client/system/info_android.go | 2 +- client/system/info_darwin.go | 2 +- client/system/info_freebsd.go | 2 +- client/system/info_linux.go | 2 +- client/system/info_windows.go | 2 +- iface/tun_android.go | 5 + iface/tun_darwin.go | 8 + iface/tun_windows.go | 15 +- management/client/grpc.go | 1 + management/proto/management.pb.go | 499 +++++++++++----------- management/proto/management.proto | 1 + management/server/account_test.go | 17 +- management/server/dns_test.go | 34 +- management/server/grpcserver.go | 17 +- management/server/nameserver_test.go | 34 +- management/server/peer.go | 22 +- management/server/route_test.go | 85 ++-- 20 files changed, 407 insertions(+), 350 deletions(-) diff --git a/client/firewall/iptables/manager_linux.go b/client/firewall/iptables/manager_linux.go index 4ce904df6c5..e90034b1566 100644 --- a/client/firewall/iptables/manager_linux.go +++ b/client/firewall/iptables/manager_linux.go @@ -44,6 +44,7 @@ type Manager struct { type iFaceMapper interface { Name() string Address() iface.WGAddress + Address6() *iface.WGAddress IsUserspaceBind() bool } @@ -57,9 +58,11 @@ func Create(wgIface iFaceMapper, ipv6Supported bool) (*Manager, error) { m := &Manager{ wgIface: wgIface, inputDefaultRuleSpecs: []string{ - "-i", wgIface.Name(), "-j", ChainInputFilterName, "-s", wgIface.Address().String()}, + "-i", wgIface.Name(), "-j", ChainInputFilterName, "-s", wgIface.Address().String(), + "-i", wgIface.Name(), "-j", ChainInputFilterName, "-s", wgIface.Address6().String()}, outputDefaultRuleSpecs: []string{ - "-o", wgIface.Name(), "-j", ChainOutputFilterName, "-d", wgIface.Address().String()}, + "-o", wgIface.Name(), "-j", ChainOutputFilterName, "-d", wgIface.Address().String(), + "-o", wgIface.Name(), "-j", ChainInputFilterName, "-s", wgIface.Address6().String()}, rulesets: make(map[string]ruleset), } diff --git a/client/firewall/uspfilter/uspfilter.go b/client/firewall/uspfilter/uspfilter.go index 6fd11e65204..b93f1bebf67 100644 --- a/client/firewall/uspfilter/uspfilter.go +++ b/client/firewall/uspfilter/uspfilter.go @@ -20,6 +20,7 @@ const layerTypeAll = 0 type IFaceMapper interface { SetFilter(iface.PacketFilter) error Address() iface.WGAddress + Address6() *iface.WGAddress } // RuleSet is a set of rules grouped by a string key diff --git a/client/system/info.go b/client/system/info.go index a495ed1e91a..447be580e88 100644 --- a/client/system/info.go +++ b/client/system/info.go @@ -25,6 +25,7 @@ type Info struct { CPUs int WiretrusteeVersion string UIVersion string + Ipv6Supported bool } // extractUserAgent extracts Netbird's agent (client) name and version from the outgoing context diff --git a/client/system/info_android.go b/client/system/info_android.go index 9a1a7befb30..572a5d15128 100644 --- a/client/system/info_android.go +++ b/client/system/info_android.go @@ -23,7 +23,7 @@ func GetInfo(ctx context.Context) *Info { kernel = osInfo[1] } - gio := &Info{Kernel: kernel, Core: osVersion(), Platform: "unknown", OS: "android", OSVersion: osVersion(), GoOS: runtime.GOOS, CPUs: runtime.NumCPU()} + gio := &Info{Kernel: kernel, Core: osVersion(), Platform: "unknown", OS: "android", OSVersion: osVersion(), GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), Ipv6Supported: false} gio.Hostname = extractDeviceName(ctx, "android") gio.WiretrusteeVersion = version.NetbirdVersion() gio.UIVersion = extractUserAgent(ctx) diff --git a/client/system/info_darwin.go b/client/system/info_darwin.go index e153539bb20..37190b99ed1 100644 --- a/client/system/info_darwin.go +++ b/client/system/info_darwin.go @@ -31,7 +31,7 @@ func GetInfo(ctx context.Context) *Info { log.Warnf("got an error while retrieving macOS version with sw_vers, error: %s. Using darwin version instead.\n", err) swVersion = []byte(release) } - gio := &Info{Kernel: sysName, OSVersion: strings.TrimSpace(string(swVersion)), Core: release, Platform: machine, OS: sysName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU()} + gio := &Info{Kernel: sysName, OSVersion: strings.TrimSpace(string(swVersion)), Core: release, Platform: machine, OS: sysName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), Ipv6Supported: true} systemHostname, _ := os.Hostname() gio.Hostname = extractDeviceName(ctx, systemHostname) gio.WiretrusteeVersion = version.NetbirdVersion() diff --git a/client/system/info_freebsd.go b/client/system/info_freebsd.go index 6c2d8a70165..7326af7f4e9 100644 --- a/client/system/info_freebsd.go +++ b/client/system/info_freebsd.go @@ -23,7 +23,7 @@ func GetInfo(ctx context.Context) *Info { osStr := strings.Replace(out, "\n", "", -1) osStr = strings.Replace(osStr, "\r\n", "", -1) osInfo := strings.Split(osStr, " ") - gio := &Info{Kernel: osInfo[0], Core: osInfo[1], Platform: runtime.GOARCH, OS: osInfo[2], GoOS: runtime.GOOS, CPUs: runtime.NumCPU()} + gio := &Info{Kernel: osInfo[0], Core: osInfo[1], Platform: runtime.GOARCH, OS: osInfo[2], GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), Ipv6Supported: true} systemHostname, _ := os.Hostname() gio.Hostname = extractDeviceName(ctx, systemHostname) gio.WiretrusteeVersion = version.NetbirdVersion() diff --git a/client/system/info_linux.go b/client/system/info_linux.go index 7d43fc0356a..6b50cb90ce8 100644 --- a/client/system/info_linux.go +++ b/client/system/info_linux.go @@ -49,7 +49,7 @@ func GetInfo(ctx context.Context) *Info { if osName == "" { osName = osInfo[3] } - gio := &Info{Kernel: osInfo[0], Core: osInfo[1], Platform: osInfo[2], OS: osName, OSVersion: osVer, GoOS: runtime.GOOS, CPUs: runtime.NumCPU()} + gio := &Info{Kernel: osInfo[0], Core: osInfo[1], Platform: osInfo[2], OS: osName, OSVersion: osVer, GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), Ipv6Supported: true} systemHostname, _ := os.Hostname() gio.Hostname = extractDeviceName(ctx, systemHostname) gio.WiretrusteeVersion = version.NetbirdVersion() diff --git a/client/system/info_windows.go b/client/system/info_windows.go index c8c3276c989..0cf9a7844bd 100644 --- a/client/system/info_windows.go +++ b/client/system/info_windows.go @@ -15,7 +15,7 @@ import ( // GetInfo retrieves and parses the system information func GetInfo(ctx context.Context) *Info { ver := getOSVersion() - gio := &Info{Kernel: "windows", OSVersion: ver, Core: ver, Platform: "unknown", OS: "windows", GoOS: runtime.GOOS, CPUs: runtime.NumCPU()} + gio := &Info{Kernel: "windows", OSVersion: ver, Core: ver, Platform: "unknown", OS: "windows", GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), Ipv6Supported: true} systemHostname, _ := os.Hostname() gio.Hostname = extractDeviceName(ctx, systemHostname) gio.WiretrusteeVersion = version.NetbirdVersion() diff --git a/iface/tun_android.go b/iface/tun_android.go index 30d86e7e21b..bdae30e37a6 100644 --- a/iface/tun_android.go +++ b/iface/tun_android.go @@ -84,6 +84,11 @@ func (t *tunDevice) UpdateAddr(addr WGAddress) error { return nil } +func (t *tunDevice) UpdateAddr6(addr WGAddress) error { + // todo implement + return nil +} + func (t *tunDevice) Close() (err error) { if t.device != nil { t.device.Close() diff --git a/iface/tun_darwin.go b/iface/tun_darwin.go index a4ab2b4b13c..406b54934c2 100644 --- a/iface/tun_darwin.go +++ b/iface/tun_darwin.go @@ -24,6 +24,14 @@ func (c *tunDevice) assignAddr() error { return err } + if c.address6 != nil { + cmd := exec.Command("ifconfig", c.name, "inet6", c.address6.IP.String(), c.address6.IP.String()) + if out, err := cmd.CombinedOutput(); err != nil { + log.Infof(`adding IPv6 address command "%v" failed with output %s and error: `, cmd.String(), out) + return err + } + } + routeCmd := exec.Command("route", "add", "-net", c.address.Network.String(), "-interface", c.name) if out, err := routeCmd.CombinedOutput(); err != nil { log.Printf(`adding route command "%v" failed with output %s and error: `, routeCmd.String(), out) diff --git a/iface/tun_windows.go b/iface/tun_windows.go index a81702df103..e34d8c13e18 100644 --- a/iface/tun_windows.go +++ b/iface/tun_windows.go @@ -19,6 +19,7 @@ import ( type tunDevice struct { name string address WGAddress + address6 *WGAddress netInterface NetInterface iceBind *bind.ICEBind mtu int @@ -114,6 +115,11 @@ func (c *tunDevice) UpdateAddr(address WGAddress) error { return c.assignAddr() } +func (c *tunDevice) UpdateAddr6(address6 WGAddress) error { + c.address6 = address6 + return c.assignAddr() +} + func (c *tunDevice) WgAddress() WGAddress { return c.address } @@ -161,8 +167,15 @@ func (c *tunDevice) getInterfaceGUIDString() (string, error) { func (c *tunDevice) assignAddr() error { tunDev := c.netInterface.(*tun.NativeTun) luid := winipcfg.LUID(tunDev.LUID()) + addresses := []netip.Prefix{netip.MustParsePrefix(c.address.String())} log.Debugf("adding address %s to interface: %s", c.address.IP, c.name) - return luid.SetIPAddresses([]netip.Prefix{netip.MustParsePrefix(c.address.String())}) + + if c.address6 != nil { + addresses = append(addresses, netip.MustParsePrefix(c.address6.String())) + log.Debugf("adding IPv6 address %s to interface: %s", c.address6.IP, c.name) + } + + return luid.SetIPAddresses(addresses) } // getUAPI returns a Listener diff --git a/management/client/grpc.go b/management/client/grpc.go index ddb420ee20e..44d3ab2507f 100644 --- a/management/client/grpc.go +++ b/management/client/grpc.go @@ -433,5 +433,6 @@ func infoToMetaData(info *system.Info) *proto.PeerSystemMeta { Kernel: info.Kernel, WiretrusteeVersion: info.WiretrusteeVersion, UiVersion: info.UIVersion, + Ipv6Supported: info.Ipv6Supported, } } diff --git a/management/proto/management.pb.go b/management/proto/management.pb.go index 9be1eeeae4a..beae9b56b12 100644 --- a/management/proto/management.pb.go +++ b/management/proto/management.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v4.24.4 +// protoc v4.25.0 // source: management.proto package proto @@ -603,6 +603,7 @@ type PeerSystemMeta struct { OS string `protobuf:"bytes,6,opt,name=OS,proto3" json:"OS,omitempty"` WiretrusteeVersion string `protobuf:"bytes,7,opt,name=wiretrusteeVersion,proto3" json:"wiretrusteeVersion,omitempty"` UiVersion string `protobuf:"bytes,8,opt,name=uiVersion,proto3" json:"uiVersion,omitempty"` + Ipv6Supported bool `protobuf:"varint,9,opt,name=ipv6Supported,proto3" json:"ipv6Supported,omitempty"` } func (x *PeerSystemMeta) Reset() { @@ -693,6 +694,13 @@ func (x *PeerSystemMeta) GetUiVersion() string { return "" } +func (x *PeerSystemMeta) GetIpv6Supported() bool { + if x != nil { + return x.Ipv6Supported + } + return false +} + type LoginResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2274,7 +2282,7 @@ var file_management_proto_rawDesc = []byte{ 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0xe6, 0x01, 0x0a, 0x0e, + 0x0c, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x8c, 0x02, 0x0a, 0x0e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x6f, @@ -2289,259 +2297,262 @@ var file_management_proto_rawDesc = []byte{ 0x28, 0x09, 0x52, 0x12, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x69, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x94, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, - 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x57, - 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x79, 0x0a, 0x11, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x38, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, - 0xa8, 0x01, 0x0a, 0x11, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x0a, 0x05, 0x73, 0x74, 0x75, 0x6e, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x73, 0x74, - 0x75, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x05, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x05, 0x74, 0x75, 0x72, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x69, - 0x67, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x69, 0x70, 0x76, 0x36, 0x53, 0x75, 0x70, 0x70, + 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x70, 0x76, + 0x36, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x22, 0x94, 0x01, 0x0a, 0x0d, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x11, + 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x65, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x77, 0x69, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, + 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, + 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x22, 0x79, 0x0a, 0x11, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x38, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x65, 0x73, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, + 0x41, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x07, 0x0a, 0x05, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xa8, 0x01, 0x0a, 0x11, 0x57, 0x69, 0x72, 0x65, 0x74, 0x72, + 0x75, 0x73, 0x74, 0x65, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x0a, 0x05, 0x73, + 0x74, 0x75, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x22, 0x98, 0x01, 0x0a, 0x0a, 0x48, - 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x3b, 0x0a, 0x08, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0x3b, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, - 0x03, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x02, - 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x44, - 0x54, 0x4c, 0x53, 0x10, 0x04, 0x22, 0x7d, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, - 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, - 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x69, 0x67, 0x52, 0x05, 0x73, 0x74, 0x75, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x05, 0x74, 0x75, 0x72, + 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x48, + 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x74, 0x75, 0x72, 0x6e, 0x73, + 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, - 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, 0x22, 0x9d, 0x01, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, - 0x03, 0x64, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x6e, 0x73, 0x12, - 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x36, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x36, 0x22, 0xe2, 0x03, 0x0a, 0x0a, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x4d, 0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x0a, 0x70, - 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, - 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, - 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, - 0x65, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, - 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x33, - 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, - 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x40, 0x0a, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, - 0x65, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, - 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, - 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, - 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, - 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, - 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, - 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x14, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, - 0x65, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x97, 0x01, 0x0a, 0x10, 0x52, 0x65, - 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, - 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c, - 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, - 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, - 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, - 0x71, 0x64, 0x6e, 0x22, 0x49, 0x0a, 0x09, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x20, - 0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x48, 0x0a, 0x08, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, - 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, - 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x08, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x16, 0x0a, 0x08, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, - 0x10, 0x00, 0x22, 0x1e, 0x0a, 0x1c, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x5b, 0x0a, 0x15, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x42, 0x0a, 0x0e, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, - 0xea, 0x02, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, - 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, - 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, - 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, - 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, - 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, - 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x53, 0x63, 0x6f, - 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x55, 0x73, 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x52, 0x65, 0x64, 0x69, - 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, - 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x22, 0xb5, 0x01, 0x0a, - 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, - 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x1e, - 0x0a, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, 0x64, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x4e, - 0x65, 0x74, 0x49, 0x44, 0x22, 0xb4, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x47, 0x0a, 0x10, 0x4e, 0x61, 0x6d, 0x65, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, - 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x0b, - 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x22, 0x58, 0x0a, 0x0a, 0x43, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, - 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x12, 0x32, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, + 0x22, 0x98, 0x01, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, + 0x69, 0x12, 0x3b, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x22, 0x3b, + 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, + 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, + 0x48, 0x54, 0x54, 0x50, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, + 0x03, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x54, 0x4c, 0x53, 0x10, 0x04, 0x22, 0x7d, 0x0a, 0x13, 0x50, + 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x36, 0x0a, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, + 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, + 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x9d, 0x01, 0x0a, 0x0a, 0x50, + 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x64, 0x6e, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, + 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x12, 0x1a, + 0x0a, 0x08, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x36, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x36, 0x22, 0xe2, 0x03, 0x0a, 0x0a, 0x4e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4d, 0x61, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x65, 0x72, + 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x53, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, + 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3e, 0x0a, 0x0b, 0x72, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, 0x6f, + 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x72, 0x65, + 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, + 0x72, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x06, 0x52, 0x6f, 0x75, + 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x06, 0x52, 0x6f, + 0x75, 0x74, 0x65, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, + 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x40, 0x0a, 0x0c, 0x6f, 0x66, 0x66, + 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x6f, + 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x46, + 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x52, 0x65, - 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x74, 0x0a, 0x0c, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x43, 0x6c, - 0x61, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x22, 0xb3, 0x01, 0x0a, 0x0f, - 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, - 0x38, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0b, 0x4e, 0x61, - 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x72, 0x69, - 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x50, 0x72, 0x69, 0x6d, - 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x32, 0x0a, - 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x53, 0x65, 0x61, - 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x64, 0x22, 0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, - 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, - 0x16, 0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x8a, 0x03, 0x0a, 0x0c, - 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, - 0x65, 0x72, 0x49, 0x50, 0x12, 0x40, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x46, 0x69, + 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x66, + 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x49, 0x73, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x66, 0x69, 0x72, 0x65, 0x77, + 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x49, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, + 0x97, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x50, 0x65, 0x65, 0x72, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, + 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, + 0x12, 0x33, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x53, 0x53, 0x48, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x73, 0x73, 0x68, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0x49, 0x0a, 0x09, 0x53, 0x53, 0x48, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x45, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x73, 0x68, 0x45, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, 0x62, + 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x73, 0x68, 0x50, 0x75, + 0x62, 0x4b, 0x65, 0x79, 0x22, 0x20, 0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, + 0x6f, 0x77, 0x12, 0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0e, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x22, 0x16, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, 0x0a, 0x06, + 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x1e, 0x0a, 0x1c, 0x50, 0x4b, 0x43, 0x45, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, + 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5b, 0x0a, 0x15, 0x50, 0x4b, 0x43, 0x45, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, + 0x77, 0x12, 0x42, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xea, 0x02, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x12, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x49, + 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x55, 0x73, + 0x65, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x15, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x22, + 0x0a, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, 0x4c, 0x73, 0x18, 0x0a, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x52, + 0x4c, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, + 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, + 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x4e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, + 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, 0x72, 0x61, + 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x4d, 0x61, 0x73, 0x71, 0x75, 0x65, + 0x72, 0x61, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x65, 0x74, 0x49, 0x44, 0x22, 0xb4, 0x01, 0x0a, 0x09, 0x44, + 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x47, + 0x0a, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x10, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x38, 0x0a, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, + 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x5a, 0x6f, 0x6e, 0x65, 0x52, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, + 0x73, 0x22, 0x58, 0x0a, 0x0a, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5a, 0x6f, 0x6e, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x32, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x74, 0x0a, 0x0c, 0x53, + 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x54, 0x54, 0x4c, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x14, 0x0a, 0x05, 0x52, + 0x44, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x44, 0x61, 0x74, + 0x61, 0x22, 0xb3, 0x01, 0x0a, 0x0f, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x38, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, + 0x18, 0x0a, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, + 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, + 0x74, 0x22, 0x8a, 0x03, 0x0a, 0x0c, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, + 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x12, 0x40, 0x0a, 0x09, 0x44, 0x69, + 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, + 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x06, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, + 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, - 0x65, 0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x44, 0x69, 0x72, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, - 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x3d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, - 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, - 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, - 0x72, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x36, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x36, 0x22, 0x1c, 0x0a, 0x09, - 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, - 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x22, 0x1e, 0x0a, 0x06, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, - 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, 0x22, 0x3c, 0x0a, 0x08, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, - 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, - 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, - 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x32, 0xd1, 0x03, 0x0a, 0x11, 0x4d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, - 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, + 0x49, 0x50, 0x36, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x50, 0x65, 0x65, 0x72, 0x49, + 0x50, 0x36, 0x22, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, + 0x22, 0x1e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, + 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, + 0x22, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, + 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, + 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, + 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x32, 0xd1, + 0x03, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, - 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, - 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, - 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, - 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, - 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, + 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, + 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, + 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, + 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, - 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, - 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/management/proto/management.proto b/management/proto/management.proto index 1d7bcdb69a7..6f2bec21e39 100644 --- a/management/proto/management.proto +++ b/management/proto/management.proto @@ -102,6 +102,7 @@ message PeerSystemMeta { string OS = 6; string wiretrusteeVersion = 7; string uiVersion = 8; + bool ipv6Supported = 9; } message LoginResponse { diff --git a/management/server/account_test.go b/management/server/account_test.go index c8a8a5dc96a..11313b950eb 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -29,14 +29,15 @@ func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Ac Key: "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8=", Name: "test-host@netbird.io", Meta: PeerSystemMeta{ - Hostname: "test-host@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", + Hostname: "test-host@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: false, }, } diff --git a/management/server/dns_test.go b/management/server/dns_test.go index a2c9d3aa2f7..54f7da27307 100644 --- a/management/server/dns_test.go +++ b/management/server/dns_test.go @@ -209,14 +209,15 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro Key: dnsPeer1Key, Name: "test-host1@netbird.io", Meta: PeerSystemMeta{ - Hostname: "test-host1@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", + Hostname: "test-host1@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: false, }, DNSLabel: dnsPeer1Key, } @@ -224,14 +225,15 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro Key: dnsPeer2Key, Name: "test-host2@netbird.io", Meta: PeerSystemMeta{ - Hostname: "test-host2@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", + Hostname: "test-host2@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: false, }, DNSLabel: dnsPeer2Key, } diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index b91b2f3748a..4a626bb9053 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -245,14 +245,15 @@ func mapError(err error) error { func extractPeerMeta(loginReq *proto.LoginRequest) PeerSystemMeta { return PeerSystemMeta{ - Hostname: loginReq.GetMeta().GetHostname(), - GoOS: loginReq.GetMeta().GetGoOS(), - Kernel: loginReq.GetMeta().GetKernel(), - Core: loginReq.GetMeta().GetCore(), - Platform: loginReq.GetMeta().GetPlatform(), - OS: loginReq.GetMeta().GetOS(), - WtVersion: loginReq.GetMeta().GetWiretrusteeVersion(), - UIVersion: loginReq.GetMeta().GetUiVersion(), + Hostname: loginReq.GetMeta().GetHostname(), + GoOS: loginReq.GetMeta().GetGoOS(), + Kernel: loginReq.GetMeta().GetKernel(), + Core: loginReq.GetMeta().GetCore(), + Platform: loginReq.GetMeta().GetPlatform(), + OS: loginReq.GetMeta().GetOS(), + WtVersion: loginReq.GetMeta().GetWiretrusteeVersion(), + UIVersion: loginReq.GetMeta().GetUiVersion(), + Ipv6Supported: loginReq.GetMeta().GetIpv6Supported(), } } diff --git a/management/server/nameserver_test.go b/management/server/nameserver_test.go index 6210ae538fb..3f0d9333604 100644 --- a/management/server/nameserver_test.go +++ b/management/server/nameserver_test.go @@ -764,28 +764,30 @@ func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error Key: nsGroupPeer1Key, Name: "test-host1@netbird.io", Meta: PeerSystemMeta{ - Hostname: "test-host1@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", + Hostname: "test-host1@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: false, }, } peer2 := &Peer{ Key: nsGroupPeer2Key, Name: "test-host2@netbird.io", Meta: PeerSystemMeta{ - Hostname: "test-host2@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", + Hostname: "test-host2@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: false, }, } existingNSGroup := nbdns.NameServerGroup{ diff --git a/management/server/peer.go b/management/server/peer.go index aea312dfdcd..1a4c09ed851 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -18,14 +18,15 @@ import ( // PeerSystemMeta is a metadata of a Peer machine system type PeerSystemMeta struct { - Hostname string - GoOS string - Kernel string - Core string - Platform string - OS string - WtVersion string - UIVersion string + Hostname string + GoOS string + Kernel string + Core string + Platform string + OS string + WtVersion string + UIVersion string + Ipv6Supported bool } func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool { @@ -36,7 +37,8 @@ func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool { p.Platform == other.Platform && p.OS == other.OS && p.WtVersion == other.WtVersion && - p.UIVersion == other.UIVersion + p.UIVersion == other.UIVersion && + p.Ipv6Supported == other.Ipv6Supported } type PeerStatus struct { @@ -539,7 +541,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* return nil, nil, err } var nextIp6 *net.IP = nil - if network.Net6 != nil { + if network.Net6 != nil && peer.Meta.Ipv6Supported { nextIp6tmp, err := AllocatePeerIP6(*network.Net6, takenIps) if err != nil { return nil, nil, err diff --git a/management/server/route_test.go b/management/server/route_test.go index efd73d6c2d1..a1f36583815 100644 --- a/management/server/route_test.go +++ b/management/server/route_test.go @@ -1050,14 +1050,15 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er Name: "test-host1@netbird.io", UserID: userID, Meta: PeerSystemMeta{ - Hostname: "test-host1@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", + Hostname: "test-host1@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: false, }, } account.Peers[peer1.ID] = peer1 @@ -1075,14 +1076,15 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er Name: "test-host2@netbird.io", UserID: userID, Meta: PeerSystemMeta{ - Hostname: "test-host2@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", + Hostname: "test-host2@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: false, }, } account.Peers[peer2.ID] = peer2 @@ -1100,14 +1102,15 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er Name: "test-host3@netbird.io", UserID: userID, Meta: PeerSystemMeta{ - Hostname: "test-host3@netbird.io", - GoOS: "darwin", - Kernel: "Darwin", - Core: "13.4.1", - Platform: "arm64", - OS: "darwin", - WtVersion: "development", - UIVersion: "development", + Hostname: "test-host3@netbird.io", + GoOS: "darwin", + Kernel: "Darwin", + Core: "13.4.1", + Platform: "arm64", + OS: "darwin", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: false, }, } account.Peers[peer3.ID] = peer3 @@ -1125,14 +1128,15 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er Name: "test-host4@netbird.io", UserID: userID, Meta: PeerSystemMeta{ - Hostname: "test-host4@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", + Hostname: "test-host4@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: false, }, } account.Peers[peer4.ID] = peer4 @@ -1150,14 +1154,15 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er Name: "test-host4@netbird.io", UserID: userID, Meta: PeerSystemMeta{ - Hostname: "test-host4@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", + Hostname: "test-host4@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: false, }, } account.Peers[peer5.ID] = peer5 From 9bd73d47a46a024e0fd90fb13e22a3b1003fb707 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Thu, 14 Dec 2023 03:05:03 +0100 Subject: [PATCH 05/42] Feat allow configuration of IPv6 support through API, improve stability --- client/firewall/iptables/manager_linux.go | 4 +- client/internal/engine.go | 17 ++++++-- client/internal/routemanager/client.go | 6 ++- .../routemanager/systemops_nonlinux.go | 1 + .../routemanager/systemops_windows.go | 2 + client/system/info_darwin.go | 2 +- client/system/info_freebsd.go | 2 +- client/system/info_linux.go | 9 +++- client/system/info_windows.go | 2 +- iface/iface.go | 12 ++++-- iface/tun_windows.go | 19 +++++---- management/server/account.go | 39 ++++++++++++++++-- management/server/activity/codes.go | 6 +++ management/server/http/accounts_handler.go | 2 + management/server/http/api/openapi.yml | 23 +++++++++++ management/server/http/api/types.gen.go | 13 ++++++ management/server/http/peers_handler.go | 11 ++++- management/server/http/peers_handler_test.go | 8 +++- management/server/mock_server/account_mock.go | 6 +-- management/server/network.go | 28 +++++++------ management/server/peer.go | 41 +++++++++++++++++-- 21 files changed, 207 insertions(+), 46 deletions(-) diff --git a/client/firewall/iptables/manager_linux.go b/client/firewall/iptables/manager_linux.go index e90034b1566..8ccac1d4167 100644 --- a/client/firewall/iptables/manager_linux.go +++ b/client/firewall/iptables/manager_linux.go @@ -59,10 +59,10 @@ func Create(wgIface iFaceMapper, ipv6Supported bool) (*Manager, error) { wgIface: wgIface, inputDefaultRuleSpecs: []string{ "-i", wgIface.Name(), "-j", ChainInputFilterName, "-s", wgIface.Address().String(), - "-i", wgIface.Name(), "-j", ChainInputFilterName, "-s", wgIface.Address6().String()}, + "-j", ChainInputFilterName, "-s", wgIface.Address6().String()}, outputDefaultRuleSpecs: []string{ "-o", wgIface.Name(), "-j", ChainOutputFilterName, "-d", wgIface.Address().String(), - "-o", wgIface.Name(), "-j", ChainInputFilterName, "-s", wgIface.Address6().String()}, + "-j", ChainInputFilterName, "-s", wgIface.Address6().String()}, rulesets: make(map[string]ruleset), } diff --git a/client/internal/engine.go b/client/internal/engine.go index c53973a81f4..5b72f487ffd 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -516,10 +516,19 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error { e.config.WgAddr = conf.Address log.Infof("updated peer address from %s to %s", oldAddr, conf.Address) } - if e.wgInterface.Address6() != nil && e.wgInterface.Address6().String() != conf.Address6 { - oldAddr := e.wgInterface.Address6().String() - log.Debugf("updating peer IPv6 address from %s to %s", oldAddr, conf.Address6) - err := e.wgInterface.UpdateAddr6(conf.Address) + + if e.wgInterface.Address6() == nil && conf.Address6 != "" || + e.wgInterface.Address6() != nil && e.wgInterface.Address6().String() != conf.Address6 { + oldAddr := "none" + if e.wgInterface.Address6() != nil { + oldAddr = e.wgInterface.Address6().String() + } + newAddr := "none" + if conf.Address6 != "" { + newAddr = conf.Address6 + } + log.Debugf("updating peer IPv6 address from %s to %s", oldAddr, newAddr) + err := e.wgInterface.UpdateAddr6(conf.Address6) if err != nil { return err } diff --git a/client/internal/routemanager/client.go b/client/internal/routemanager/client.go index 52b2ffeac96..4d8b2ca7fba 100644 --- a/client/internal/routemanager/client.go +++ b/client/internal/routemanager/client.go @@ -108,8 +108,6 @@ func (c *clientNetwork) getBestRouteFromStatuses(routePeerStatuses map[string]ro chosen = r.ID chosenScore = tempScore } - // TODO IPv6 consider IPv6 connectivity for route selection? - // Depends on how we want to handle non-ipv6-compatible clients } if chosen == "" { @@ -223,6 +221,10 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem() error { } else { gwAddr := c.wgInterface.Address().IP.String() if c.network.Addr().Is6() { + if c.wgInterface.Address6() == nil { + return fmt.Errorf("Could not assign IPv6 route %s for peer %s because no IPv6 address is assigned", + c.network.String(), c.wgInterface.Address().IP.String()) + } gwAddr = c.wgInterface.Address6().IP.String() } diff --git a/client/internal/routemanager/systemops_nonlinux.go b/client/internal/routemanager/systemops_nonlinux.go index d882309a409..474fa7135ca 100644 --- a/client/internal/routemanager/systemops_nonlinux.go +++ b/client/internal/routemanager/systemops_nonlinux.go @@ -13,6 +13,7 @@ import ( func addToRouteTable(prefix netip.Prefix, addr string, devName string) error { // devName is ignored here, the route interface is automatically determined based on the gateway address. + // TODO connecting via IPv6 to other peers on windows doesn't work - route configuration issue? cmd := exec.Command("route", "add", prefix.String(), addr) out, err := cmd.Output() if err != nil { diff --git a/client/internal/routemanager/systemops_windows.go b/client/internal/routemanager/systemops_windows.go index 288f63df54a..2233748bfd4 100644 --- a/client/internal/routemanager/systemops_windows.go +++ b/client/internal/routemanager/systemops_windows.go @@ -6,6 +6,8 @@ package routemanager import ( "net" "net/netip" + + "github.com/yusufpapurcu/wmi" ) type Win32_IP4RouteTable struct { diff --git a/client/system/info_darwin.go b/client/system/info_darwin.go index 37190b99ed1..ae4bda108a4 100644 --- a/client/system/info_darwin.go +++ b/client/system/info_darwin.go @@ -31,7 +31,7 @@ func GetInfo(ctx context.Context) *Info { log.Warnf("got an error while retrieving macOS version with sw_vers, error: %s. Using darwin version instead.\n", err) swVersion = []byte(release) } - gio := &Info{Kernel: sysName, OSVersion: strings.TrimSpace(string(swVersion)), Core: release, Platform: machine, OS: sysName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), Ipv6Supported: true} + gio := &Info{Kernel: sysName, OSVersion: strings.TrimSpace(string(swVersion)), Core: release, Platform: machine, OS: sysName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), Ipv6Supported: false} systemHostname, _ := os.Hostname() gio.Hostname = extractDeviceName(ctx, systemHostname) gio.WiretrusteeVersion = version.NetbirdVersion() diff --git a/client/system/info_freebsd.go b/client/system/info_freebsd.go index 7326af7f4e9..120bda2937a 100644 --- a/client/system/info_freebsd.go +++ b/client/system/info_freebsd.go @@ -23,7 +23,7 @@ func GetInfo(ctx context.Context) *Info { osStr := strings.Replace(out, "\n", "", -1) osStr = strings.Replace(osStr, "\r\n", "", -1) osInfo := strings.Split(osStr, " ") - gio := &Info{Kernel: osInfo[0], Core: osInfo[1], Platform: runtime.GOARCH, OS: osInfo[2], GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), Ipv6Supported: true} + gio := &Info{Kernel: osInfo[0], Core: osInfo[1], Platform: runtime.GOARCH, OS: osInfo[2], GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), Ipv6Supported: false} systemHostname, _ := os.Hostname() gio.Hostname = extractDeviceName(ctx, systemHostname) gio.WiretrusteeVersion = version.NetbirdVersion() diff --git a/client/system/info_linux.go b/client/system/info_linux.go index 6b50cb90ce8..0a65e167afe 100644 --- a/client/system/info_linux.go +++ b/client/system/info_linux.go @@ -7,6 +7,8 @@ import ( "bytes" "context" "fmt" + "github.com/netbirdio/netbird/client/internal/checkfw" + "github.com/netbirdio/netbird/iface" "os" "os/exec" "runtime" @@ -49,7 +51,7 @@ func GetInfo(ctx context.Context) *Info { if osName == "" { osName = osInfo[3] } - gio := &Info{Kernel: osInfo[0], Core: osInfo[1], Platform: osInfo[2], OS: osName, OSVersion: osVer, GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), Ipv6Supported: true} + gio := &Info{Kernel: osInfo[0], Core: osInfo[1], Platform: osInfo[2], OS: osName, OSVersion: osVer, GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), Ipv6Supported: _checkIPv6Support()} systemHostname, _ := os.Hostname() gio.Hostname = extractDeviceName(ctx, systemHostname) gio.WiretrusteeVersion = version.NetbirdVersion() @@ -85,3 +87,8 @@ func _getReleaseInfo() string { } return out.String() } + +func _checkIPv6Support() bool { + return checkfw.Check() == checkfw.NFTABLES && + iface.WireGuardModuleIsLoaded() +} diff --git a/client/system/info_windows.go b/client/system/info_windows.go index 0cf9a7844bd..a3feab98ae1 100644 --- a/client/system/info_windows.go +++ b/client/system/info_windows.go @@ -15,7 +15,7 @@ import ( // GetInfo retrieves and parses the system information func GetInfo(ctx context.Context) *Info { ver := getOSVersion() - gio := &Info{Kernel: "windows", OSVersion: ver, Core: ver, Platform: "unknown", OS: "windows", GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), Ipv6Supported: true} + gio := &Info{Kernel: "windows", OSVersion: ver, Core: ver, Platform: "unknown", OS: "windows", GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), Ipv6Supported: false} systemHostname, _ := os.Hostname() gio.Hostname = extractDeviceName(ctx, systemHostname) gio.WiretrusteeVersion = version.NetbirdVersion() diff --git a/iface/iface.go b/iface/iface.go index 6c03808f216..6bb328e6ee9 100644 --- a/iface/iface.go +++ b/iface/iface.go @@ -78,12 +78,16 @@ func (w *WGIface) UpdateAddr6(newAddr6 string) error { w.mu.Lock() defer w.mu.Unlock() - addr, err := parseWGAddress(newAddr6) - if err != nil { - return err + var addr *WGAddress + if newAddr6 != "" { + parsedAddr, err := parseWGAddress(newAddr6) + if err != nil { + return err + } + addr = &parsedAddr } - return w.tun.UpdateAddr6(&addr) + return w.tun.UpdateAddr6(addr) } // UpdatePeer updates existing Wireguard Peer or creates a new one if doesn't exist diff --git a/iface/tun_windows.go b/iface/tun_windows.go index e34d8c13e18..602b4d9a913 100644 --- a/iface/tun_windows.go +++ b/iface/tun_windows.go @@ -28,13 +28,14 @@ type tunDevice struct { close chan struct{} } -func newTunDevice(name string, address WGAddress, mtu int, transportNet transport.Net) *tunDevice { +func newTunDevice(name string, address WGAddress, address6 *WGAddress, mtu int, transportNet transport.Net) *tunDevice { return &tunDevice{ - name: name, - address: address, - mtu: mtu, - iceBind: bind.NewICEBind(transportNet), - close: make(chan struct{}), + name: name, + address: address, + address6: address6, + mtu: mtu, + iceBind: bind.NewICEBind(transportNet), + close: make(chan struct{}), } } @@ -115,7 +116,7 @@ func (c *tunDevice) UpdateAddr(address WGAddress) error { return c.assignAddr() } -func (c *tunDevice) UpdateAddr6(address6 WGAddress) error { +func (c *tunDevice) UpdateAddr6(address6 *WGAddress) error { c.address6 = address6 return c.assignAddr() } @@ -124,6 +125,10 @@ func (c *tunDevice) WgAddress() WGAddress { return c.address } +func (c *tunDevice) WgAddress6() *WGAddress { + return c.address6 +} + func (c *tunDevice) DeviceName() string { return c.name } diff --git a/management/server/account.go b/management/server/account.go index d5fe6760640..b344c244b98 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -66,7 +66,7 @@ type AccountManager interface { GetPeers(accountID, userID string) ([]*Peer, error) MarkPeerConnected(peerKey string, connected bool) error DeletePeer(accountID, peerID, userID string) error - UpdatePeer(accountID, userID string, peer *Peer) (*Peer, error) + UpdatePeer(accountID, userID string, peer *Peer, enableV6 bool) (*Peer, error) GetNetworkMap(peerID string) (*NetworkMap, error) GetPeerNetwork(peerID string) (*Network, error) AddPeer(setupKey, userID string, peer *Peer) (*Peer, *NetworkMap, error) @@ -151,6 +151,9 @@ type Settings struct { // JWTGroupsClaimName from which we extract groups name to add it to account groups JWTGroupsClaimName string + + // AssignIPv6ByDefault determines whether hosts added to the network get assigned an IPv6 address by default. + AssignIPv6ByDefault bool } // Copy copies the Settings struct @@ -161,6 +164,7 @@ func (s *Settings) Copy() *Settings { JWTGroupsEnabled: s.JWTGroupsEnabled, JWTGroupsClaimName: s.JWTGroupsClaimName, GroupsPropagationEnabled: s.GroupsPropagationEnabled, + AssignIPv6ByDefault: s.AssignIPv6ByDefault, } } @@ -222,7 +226,8 @@ func (a *Account) getRoutesToSync(peerID string, aclPeers []*Peer) []*route.Rout groupListMap := a.getPeerGroups(peerID) for _, peer := range aclPeers { activeRoutes, _ := a.getRoutingPeerRoutes(peer.ID) - groupFilteredRoutes := a.filterRoutesByGroups(activeRoutes, groupListMap) + addressFamilyFilteredRoutes := a.filterRoutesByIPv6Enabled(activeRoutes, a.GetPeer(peerID).IP6 != nil && peer.IP6 != nil) + groupFilteredRoutes := a.filterRoutesByGroups(addressFamilyFilteredRoutes, groupListMap) filteredRoutes := a.filterRoutesFromPeersOfSameHAGroup(groupFilteredRoutes, peerRoutesMembership) routes = append(routes, filteredRoutes...) } @@ -257,6 +262,20 @@ func (a *Account) filterRoutesByGroups(routes []*route.Route, groupListMap looku return filteredRoutes } +// Filters out IPv6 routes if the peer does not support them. +func (a *Account) filterRoutesByIPv6Enabled(routes []*route.Route, v6Supported bool) []*route.Route { + if v6Supported { + return routes + } + var filteredRoutes []*route.Route + for _, route := range routes { + if route.Network.Addr().Is4() { + filteredRoutes = append(filteredRoutes, route) + } + } + return filteredRoutes +} + // getRoutingPeerRoutes returns the enabled and disabled lists of routes that the given routing peer serves // Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID. // If the given is not a routing peer, then the lists are empty. @@ -565,6 +584,17 @@ func (a *Account) getTakenIPs() []net.IP { return takenIps } +func (a *Account) getTakenIP6s() []net.IP { + var takenIps []net.IP + for _, existingPeer := range a.Peers { + if existingPeer.IP6 != nil { + takenIps = append(takenIps, *existingPeer.IP6) + } + } + + return takenIps +} + func (a *Account) getPeerDNSLabels() lookupMap { existingLabels := make(lookupMap) for _, peer := range a.Peers { @@ -881,8 +911,11 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, am.checkAndSchedulePeerLoginExpiration(account) } - updatedAccount := account.UpdateSettings(newSettings) + if oldSettings.AssignIPv6ByDefault != newSettings.AssignIPv6ByDefault { + am.storeEvent(userID, accountID, accountID, activity.AccountAssignIPv6ByDefaultUpdated, nil) + } + updatedAccount := account.UpdateSettings(newSettings) err = am.Store.SaveAccount(account) if err != nil { return nil, err diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index ce36f520fca..cd175a112d8 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -74,6 +74,10 @@ const ( PeerSSHEnabled // PeerSSHDisabled indicates that a user disabled SSH server on a peer PeerSSHDisabled + // PeerIPv6Enabled indicates that a user enabled IPv6 for a peer + PeerIPv6Enabled + // PeerIPv6Disabled indicates that a user disabled IPv6 for a peer + PeerIPv6Disabled // PeerRenamed indicates that a user renamed a peer PeerRenamed // PeerLoginExpirationEnabled indicates that a user enabled login expiration of a peer @@ -92,6 +96,8 @@ const ( AccountPeerLoginExpirationDisabled // AccountPeerLoginExpirationDurationUpdated indicates that a user updated peer login expiration duration for the account AccountPeerLoginExpirationDurationUpdated + // AccountAssignIPv6ByDefaultUpdated indicates that a user changed whether new peers get assigned an IPv6 address if supported. + AccountAssignIPv6ByDefaultUpdated // PersonalAccessTokenCreated indicates that a user created a personal access token PersonalAccessTokenCreated // PersonalAccessTokenDeleted indicates that a user deleted a personal access token diff --git a/management/server/http/accounts_handler.go b/management/server/http/accounts_handler.go index a5d7a9501c4..e2c33aa5c89 100644 --- a/management/server/http/accounts_handler.go +++ b/management/server/http/accounts_handler.go @@ -75,6 +75,7 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request) settings := &server.Settings{ PeerLoginExpirationEnabled: req.Settings.PeerLoginExpirationEnabled, PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)), + AssignIPv6ByDefault: req.Settings.AssignIpv6ByDefault, } if req.Settings.JwtGroupsEnabled != nil { @@ -107,6 +108,7 @@ func toAccountResponse(account *server.Account) *api.Account { GroupsPropagationEnabled: &account.Settings.GroupsPropagationEnabled, JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled, JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName, + AssignIpv6ByDefault: account.Settings.AssignIPv6ByDefault, }, } } diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index a0a64fd9872..00133916c8b 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -54,6 +54,10 @@ components: description: Period of time after which peer login expires (seconds). type: integer example: 43200 + assign_ipv6_by_default: + description: Whether to enable IPv6 for new hosts added to the system + type: boolean + example: false groups_propagation_enabled: description: Allows propagate the new user auto groups to peers that belongs to the user type: boolean @@ -69,6 +73,7 @@ components: required: - peer_login_expiration_enabled - peer_login_expiration + - assign_ipv6_by_default AccountRequest: type: object properties: @@ -213,10 +218,14 @@ components: login_expiration_enabled: type: boolean example: false + ipv6_enabled: + type: boolean + example: false required: - name - ssh_enabled - login_expiration_enabled + - ipv6_enabled Peer: allOf: - $ref: '#/components/schemas/PeerMinimum' @@ -226,6 +235,10 @@ components: description: Peer's IP address type: string example: 10.64.0.1 + ip6: + description: Peer's IPv6 address + type: string + example: 2001:db8::0123:4567:890a:bcde connected: description: Peer to Management connection status type: boolean @@ -264,6 +277,14 @@ components: description: Peer's desktop UI version type: string example: 0.14.0 + ipv6_supported: + description: Whether this peer supports IPv6 + type: boolean + example: true + ipv6_enabled: + description: Whether IPv6 is enabled for this peer. + type: boolean + example: true dns_label: description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud type: string @@ -291,6 +312,8 @@ components: - ssh_enabled - hostname - dns_label + - ipv6_supported + - ipv6_enabled - login_expiration_enabled - login_expired - last_login diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index ddf8ce65f34..e471ffc6601 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -131,6 +131,9 @@ type AccountRequest struct { // AccountSettings defines model for AccountSettings. type AccountSettings struct { + // AssignIpv6ByDefault Whether to enable IPv6 for new hosts added to the system + AssignIpv6ByDefault bool `json:"assign_ipv6_by_default"` + // GroupsPropagationEnabled Allows propagate the new user auto groups to peers that belongs to the user GroupsPropagationEnabled *bool `json:"groups_propagation_enabled,omitempty"` @@ -320,6 +323,15 @@ type Peer struct { // Ip Peer's IP address Ip string `json:"ip"` + // Ip6 Peer's IPv6 address + Ip6 *string `json:"ip6,omitempty"` + + // Ipv6Enabled Whether IPv6 is enabled for this peer. + Ipv6Enabled bool `json:"ipv6_enabled"` + + // Ipv6Supported Whether this peer supports IPv6 + Ipv6Supported bool `json:"ipv6_supported"` + // LastLogin Last time this peer performed log in (authentication). E.g., user authenticated. LastLogin time.Time `json:"last_login"` @@ -362,6 +374,7 @@ type PeerMinimum struct { // PeerRequest defines model for PeerRequest. type PeerRequest struct { + Ipv6Enabled bool `json:"ipv6_enabled"` LoginExpirationEnabled bool `json:"login_expiration_enabled"` Name string `json:"name"` SshEnabled bool `json:"ssh_enabled"` diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go index a485d6ccf96..693e05f4a9c 100644 --- a/management/server/http/peers_handler.go +++ b/management/server/http/peers_handler.go @@ -75,7 +75,7 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe update := &server.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name, LoginExpirationEnabled: req.LoginExpirationEnabled} - peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update) + peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update, req.Ipv6Enabled) if err != nil { util.WriteError(err, w) return @@ -185,10 +185,17 @@ func toPeerResponse(peer *server.Peer, account *server.Account, dnsDomain string fqdn = peer.DNSLabel } + var ip6 *string + if peer.IP6 != nil { + ip6string := peer.IP6.String() + ip6 = &ip6string + } + return &api.Peer{ Id: peer.ID, Name: peer.Name, Ip: peer.IP.String(), + Ip6: ip6, Connected: peer.Status.Connected, LastSeen: peer.Status.LastSeen, Os: fmt.Sprintf("%s %s", peer.Meta.OS, peer.Meta.Core), @@ -198,6 +205,8 @@ func toPeerResponse(peer *server.Peer, account *server.Account, dnsDomain string Hostname: peer.Meta.Hostname, UserId: &peer.UserID, UiVersion: &peer.Meta.UIVersion, + Ipv6Supported: peer.Meta.Ipv6Supported, + Ipv6Enabled: peer.IP6 != nil, DnsLabel: fqdn, LoginExpirationEnabled: peer.LoginExpirationEnabled, LastLogin: peer.LastLogin, diff --git a/management/server/http/peers_handler_test.go b/management/server/http/peers_handler_test.go index 1856861d549..39f99eb403d 100644 --- a/management/server/http/peers_handler_test.go +++ b/management/server/http/peers_handler_test.go @@ -29,7 +29,7 @@ const noUpdateChannelTestPeerID = "no-update-channel" func initTestMetaData(peers ...*server.Peer) *PeersHandler { return &PeersHandler{ accountManager: &mock_server.MockAccountManager{ - UpdatePeerFunc: func(accountID, userID string, update *server.Peer) (*server.Peer, error) { + UpdatePeerFunc: func(accountID, userID string, update *server.Peer, enableV6 bool) (*server.Peer, error) { var p *server.Peer for _, peer := range peers { if update.ID == peer.ID { @@ -37,6 +37,12 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler { break } } + if enableV6 && p.IP6 == nil { + ip6 := net.ParseIP("2001:db8::dead:beef") + p.IP6 = &ip6 + } else if !enableV6 { + p.IP6 = nil + } p.SSHEnabled = update.SSHEnabled p.LoginExpirationEnabled = update.LoginExpirationEnabled p.Name = update.Name diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index ea4a18f56cc..6562f37c1f4 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -45,7 +45,7 @@ type MockAccountManager struct { MarkPATUsedFunc func(pat string) error UpdatePeerMetaFunc func(peerID string, meta server.PeerSystemMeta) error UpdatePeerSSHKeyFunc func(peerID string, sshKey string) error - UpdatePeerFunc func(accountID, userID string, peer *server.Peer) (*server.Peer, error) + UpdatePeerFunc func(accountID, userID string, peer *server.Peer, enableV6 bool) (*server.Peer, error) CreateRouteFunc func(accountID, prefix, peer string, peerGroups []string, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) GetRouteFunc func(accountID, routeID, userID string) (*route.Route, error) SaveRouteFunc func(accountID, userID string, route *route.Route) error @@ -358,9 +358,9 @@ func (am *MockAccountManager) UpdatePeerSSHKey(peerID string, sshKey string) err } // UpdatePeer mocks UpdatePeerFunc function of the account manager -func (am *MockAccountManager) UpdatePeer(accountID, userID string, peer *server.Peer) (*server.Peer, error) { +func (am *MockAccountManager) UpdatePeer(accountID, userID string, peer *server.Peer, enableV6 bool) (*server.Peer, error) { if am.UpdatePeerFunc != nil { - return am.UpdatePeerFunc(accountID, userID, peer) + return am.UpdatePeerFunc(accountID, userID, peer, enableV6) } return nil, status.Errorf(codes.Unimplemented, "method UpdatePeerFunc is is not implemented") } diff --git a/management/server/network.go b/management/server/network.go index 419ca8fee06..01786320306 100644 --- a/management/server/network.go +++ b/management/server/network.go @@ -63,13 +63,7 @@ func NewNetwork(enableV6 bool) *Network { var n6 *net.IPNet = nil if enableV6 { - addrbuf := make([]byte, 16) - addrbuf[0] = 0xfd - addrbuf[1] = 0x00 - _, _ = r.Read(addrbuf[2:Subnet6Size]) - - n6tmp := iplib.NewNet6(addrbuf, Subnet6Size*8, 0).IPNet - n6 = &n6tmp + n6 = GenerateNetwork6() } return &Network{ @@ -80,6 +74,18 @@ func NewNetwork(enableV6 bool) *Network { Serial: 0} } +func GenerateNetwork6() *net.IPNet { + s := rand.NewSource(time.Now().Unix()) + r := rand.New(s) + addrbuf := make([]byte, 16) + addrbuf[0] = 0xfd + addrbuf[1] = 0x00 + _, _ = r.Read(addrbuf[2:Subnet6Size]) + + n6 := iplib.NewNet6(addrbuf, Subnet6Size*8, 0).IPNet + return &n6 +} + // IncSerial increments Serial by 1 reflecting that the network state has been changed func (n *Network) IncSerial() { n.mu.Lock() @@ -129,9 +135,7 @@ func AllocatePeerIP(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) { } // AllocatePeerIP6 pics an available IPv6 from an net.IPNet. -// This method considers already taken IPs and reuses IPs if there are gaps in takenIps -// E.g. if ipNet=100.30.0.0/16 and takenIps=[100.30.0.1, 100.30.0.4] then the result would be 100.30.0.2 or 100.30.0.3 -// TODO docs, and recheck if there might be a duplicate issue here? +// This method considers already taken IPs and reuses IPs if there are gaps in takenIps. func AllocatePeerIP6(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) { takenIPMap := make(map[string]struct{}) @@ -148,11 +152,11 @@ func AllocatePeerIP6(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) { // TODO for small subnet sizes, randomly generating values until we don't get a duplicate is inefficient and could // lead to many loop iterations, using a method similar to IPv4 would be preferable here. - addrbuf := ipNet.IP.To16() + addrbuf := make(net.IP, 16) + copy(addrbuf, ipNet.IP.To16()) for duplicate := true; duplicate; _, duplicate = takenIPMap[addrbuf.String()] { _, _ = r.Read(addrbuf[(maskSize / 8):16]) } - return addrbuf, nil } diff --git a/management/server/peer.go b/management/server/peer.go index 1a4c09ed851..a3e8dfbd45f 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -294,8 +294,8 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected return nil } -// UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled, and Peer.LoginExpirationEnabled can be updated. -func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Peer) (*Peer, error) { +// UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled and Peer.LoginExpirationEnabled can be updated. +func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Peer, enableV6 bool) (*Peer, error) { unlock := am.Store.AcquireAccountLock(accountID) defer unlock() @@ -309,6 +309,24 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Pe return nil, status.Errorf(status.NotFound, "peer %s not found", update.ID) } + if enableV6 && peer.IP6 == nil { + if !peer.Meta.Ipv6Supported { + return nil, status.Errorf(status.PreconditionFailed, "failed allocating new IPv6 for peer %s - peer does not support IPv6", peer.Name) + } + if account.Network.Net6 == nil { + account.Network.Net6 = GenerateNetwork6() + } + v6tmp, err := AllocatePeerIP6(*account.Network.Net6, account.getTakenIP6s()) + if err != nil { + return nil, err + } + peer.IP6 = &v6tmp + am.storeEvent(userID, peer.IP6.String(), accountID, activity.PeerIPv6Enabled, peer.EventMeta(am.GetDNSDomain())) + } else if !enableV6 && peer.IP6 != nil { + am.storeEvent(userID, peer.IP6.String(), accountID, activity.PeerIPv6Disabled, peer.EventMeta(am.GetDNSDomain())) + peer.IP6 = nil + } + if peer.SSHEnabled != update.SSHEnabled { peer.SSHEnabled = update.SSHEnabled event := activity.PeerSSHEnabled @@ -540,8 +558,12 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* if err != nil { return nil, nil, err } + var nextIp6 *net.IP = nil - if network.Net6 != nil && peer.Meta.Ipv6Supported { + if account.Settings.AssignIPv6ByDefault && peer.Meta.Ipv6Supported { + if network.Net6 == nil { + network.Net6 = GenerateNetwork6() + } nextIp6tmp, err := AllocatePeerIP6(*network.Net6, takenIps) if err != nil { return nil, nil, err @@ -708,6 +730,11 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*Peer, *NetworkMap, shouldStoreAccount = true } + updated = disableNoLongerSupportedFeatures(peer) + if updated { + shouldStoreAccount = true + } + peer, err = am.checkAndUpdatePeerSSHKey(peer, account, login.SSHKey) if err != nil { return nil, nil, err @@ -726,6 +753,14 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*Peer, *NetworkMap, return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil } +func disableNoLongerSupportedFeatures(peer *Peer) bool { + if !peer.Meta.Ipv6Supported && peer.IP6 != nil { + peer.IP6 = nil + return true + } + return false +} + func checkIfPeerOwnerIsBlocked(peer *Peer, account *Account) error { if peer.AddedWithSSOLogin() { user, err := account.FindUser(peer.UserID) From e6c87afdd1ee5391c2cd63ed14e96a5c7f4abe3f Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Sun, 24 Dec 2023 03:47:26 +0100 Subject: [PATCH 06/42] Feat add IPv6 support to new firewall implementation --- client/firewall/nftables/acl_linux.go | 417 ++++++++++++++---- client/firewall/nftables/manager_linux.go | 27 +- client/firewall/nftables/route_linux.go | 153 +++++-- .../routemanager/server_nonandroid.go | 23 +- management/server/http/peers_handler.go | 2 + 5 files changed, 493 insertions(+), 129 deletions(-) diff --git a/client/firewall/nftables/acl_linux.go b/client/firewall/nftables/acl_linux.go index 52d378963b4..4e8a6a9a941 100644 --- a/client/firewall/nftables/acl_linux.go +++ b/client/firewall/nftables/acl_linux.go @@ -44,14 +44,20 @@ type AclManager struct { wgIface iFaceMapper routeingFwChainName string - workTable *nftables.Table - chainInputRules *nftables.Chain - chainOutputRules *nftables.Chain - chainFwFilter *nftables.Chain - chainPrerouting *nftables.Chain - - ipsetStore *ipsetStore - rules map[string]*Rule + workTable *nftables.Table + workTable6 *nftables.Table + chainInputRules *nftables.Chain + chainOutputRules *nftables.Chain + chainFwFilter *nftables.Chain + chainPrerouting *nftables.Chain + chainInputRules6 *nftables.Chain + chainOutputRules6 *nftables.Chain + chainFwFilter6 *nftables.Chain + chainPrerouting6 *nftables.Chain + + ipsetStore *ipsetStore + ipsetStore6 *ipsetStore + rules map[string]*Rule } // iFaceMapper defines subset methods of interface required for manager @@ -61,7 +67,7 @@ type iFaceMapper interface { Address6() *iface.WGAddress } -func newAclManager(table *nftables.Table, wgIface iFaceMapper, routeingFwChainName string) (*AclManager, error) { +func newAclManager(table *nftables.Table, table6 *nftables.Table, wgIface iFaceMapper, routeingFwChainName string) (*AclManager, error) { // sConn is used for creating sets and adding/removing elements from them // it's differ then rConn (which does create new conn for each flush operation) // and is permanent. Using same connection for booth type of operations @@ -76,10 +82,12 @@ func newAclManager(table *nftables.Table, wgIface iFaceMapper, routeingFwChainNa sConn: sConn, wgIface: wgIface, workTable: table, + workTable6: table6, routeingFwChainName: routeingFwChainName, - ipsetStore: newIpsetStore(), - rules: make(map[string]*Rule), + ipsetStore: newIpsetStore(), + ipsetStore6: newIpsetStore(), + rules: make(map[string]*Rule), } err = m.createDefaultChains() @@ -158,7 +166,11 @@ func (m *AclManager) DeleteRule(rule firewall.Rule) error { return m.rConn.Flush() } if _, ok := ips[r.ip.String()]; ok { - err := m.sConn.SetDeleteElements(r.nftSet, []nftables.SetElement{{Key: r.ip.To4()}}) + rawIP := r.ip.To4() + if rawIP == nil { + rawIP = r.ip.To16() + } + err := m.sConn.SetDeleteElements(r.nftSet, []nftables.SetElement{{Key: rawIP}}) if err != nil { log.Errorf("delete elements for set %q: %v", r.nftSet.Name, err) } @@ -384,8 +396,6 @@ func (m *AclManager) addIOFiltering(ip net.IP, proto firewall.Protocol, sPort *f return rule, nil } -// TODO IPv6, like, everywhere here... - func (m *AclManager) addPreroutingFiltering(ipset *nftables.Set, proto firewall.Protocol, port *firewall.Port, ip net.IP) (*Rule, error) { var protoData []byte switch proto { @@ -412,6 +422,18 @@ func (m *AclManager) addPreroutingFiltering(ipset *nftables.Set, proto firewall. var ipExpression expr.Any // add individual IP for match if no ipset defined rawIP := ip.To4() + if rawIP == nil { + rawIP = ip.To16() + } + addrLen := uint32(len(rawIP)) + // source address position + srcAddrOffset := uint32(12) + dstAddrOffset := uint32(16) + if addrLen == 16 { + srcAddrOffset = uint32(8) + dstAddrOffset = uint32(24) + } + if ipset == nil { ipExpression = &expr.Cmp{ Op: expr.CmpOpEq, @@ -426,30 +448,33 @@ func (m *AclManager) addPreroutingFiltering(ipset *nftables.Set, proto firewall. } } + ifaceRawIP := m.wgIface.Address().IP.To4() + if addrLen == 16 { + ifaceRawIP = m.wgIface.Address6().IP.To16() + } + expressions := []expr.Any{ &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, - Offset: 12, - Len: 4, + Offset: srcAddrOffset, + Len: addrLen, }, ipExpression, &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, - Offset: 16, - Len: 4, + Offset: dstAddrOffset, + Len: addrLen, }, &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, - Data: m.wgIface.Address().IP.To4(), + Data: ifaceRawIP, }, - &expr.Payload{ - DestRegister: 1, - Base: expr.PayloadBaseNetworkHeader, - Offset: uint32(9), - Len: uint32(1), + &expr.Meta{ + Key: expr.MetaKeyL4PROTO, + Register: 1, }, &expr.Cmp{ Register: 1, @@ -514,44 +539,53 @@ func (m *AclManager) addPreroutingFiltering(ipset *nftables.Set, proto firewall. func (m *AclManager) createDefaultChains() (err error) { // chainNameInputRules - chain := m.createChain(chainNameInputRules) + chain, chain6 := m.createChain(chainNameInputRules) err = m.rConn.Flush() if err != nil { log.Debugf("failed to create chain (%s): %s", chain.Name, err) return err } m.chainInputRules = chain + m.chainInputRules6 = chain6 // chainNameOutputRules - chain = m.createChain(chainNameOutputRules) + chain, chain6 = m.createChain(chainNameOutputRules) err = m.rConn.Flush() if err != nil { log.Debugf("failed to create chain (%s): %s", chainNameOutputRules, err) return err } m.chainOutputRules = chain + m.chainOutputRules6 = chain6 // netbird-acl-input-filter // type filter hook input priority filter; policy accept; - chain = m.createFilterChainWithHook(chainNameInputFilter, nftables.ChainHookInput) + chain, chain6 = m.createFilterChainWithHook(chainNameInputFilter, nftables.ChainHookInput) //netbird-acl-input-filter iifname "wt0" ip saddr 100.72.0.0/16 ip daddr != 100.72.0.0/16 accept m.addRouteAllowRule(chain, expr.MetaKeyIIFNAME) m.addFwdAllow(chain, expr.MetaKeyIIFNAME) m.addJumpRule(chain, m.chainInputRules.Name, expr.MetaKeyIIFNAME) // to netbird-acl-input-rules m.addDropExpressions(chain, expr.MetaKeyIIFNAME) - err = m.rConn.Flush() - if err != nil { - log.Debugf("failed to create chain (%s): %s", chain.Name, err) - return err + if chain6 != nil { + m.addRouteAllowRule(chain6, expr.MetaKeyIIFNAME) + m.addFwdAllow(chain6, expr.MetaKeyIIFNAME) + m.addJumpRule(chain6, m.chainInputRules6.Name, expr.MetaKeyIIFNAME) // to netbird-acl-input-rules + m.addDropExpressions(chain6, expr.MetaKeyIIFNAME) } // netbird-acl-output-filter // type filter hook output priority filter; policy accept; - chain = m.createFilterChainWithHook(chainNameOutputFilter, nftables.ChainHookOutput) + chain, chain6 = m.createFilterChainWithHook(chainNameOutputFilter, nftables.ChainHookOutput) m.addRouteAllowRule(chain, expr.MetaKeyOIFNAME) m.addFwdAllow(chain, expr.MetaKeyOIFNAME) m.addJumpRule(chain, m.chainOutputRules.Name, expr.MetaKeyOIFNAME) // to netbird-acl-output-rules m.addDropExpressions(chain, expr.MetaKeyOIFNAME) + if chain6 != nil { + m.addRouteAllowRule(chain6, expr.MetaKeyOIFNAME) + m.addFwdAllow(chain6, expr.MetaKeyOIFNAME) + m.addJumpRule(chain6, m.chainOutputRules6.Name, expr.MetaKeyOIFNAME) // to netbird-acl-output-rules + m.addDropExpressions(chain6, expr.MetaKeyOIFNAME) + } err = m.rConn.Flush() if err != nil { log.Debugf("failed to create chain (%s): %s", chainNameOutputFilter, err) @@ -559,7 +593,7 @@ func (m *AclManager) createDefaultChains() (err error) { } // netbird-acl-forward-filter - m.chainFwFilter = m.createFilterChainWithHook(chainNameForwardFilter, nftables.ChainHookForward) + m.chainFwFilter, m.chainFwFilter6 = m.createFilterChainWithHook(chainNameForwardFilter, nftables.ChainHookForward) m.addJumpRulesToRtForward() // to m.addMarkAccept() m.addJumpRuleToInputChain() // to netbird-acl-input-rules @@ -572,7 +606,7 @@ func (m *AclManager) createDefaultChains() (err error) { // netbird-acl-output-filter // type filter hook output priority filter; policy accept; - m.chainPrerouting = m.createPreroutingMangle() + m.chainPrerouting, m.chainPrerouting6 = m.createPreroutingMangle() err = m.rConn.Flush() if err != nil { log.Debugf("failed to create chain (%s): %s", m.chainPrerouting.Name, err) @@ -601,6 +635,15 @@ func (m *AclManager) addJumpRulesToRtForward() { Exprs: expressions, }) + if m.workTable6 != nil { + + _ = m.rConn.AddRule(&nftables.Rule{ + Table: m.workTable6, + Chain: m.chainFwFilter6, + Exprs: expressions, + }) + } + expressions = []expr.Any{ &expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1}, &expr.Cmp{ @@ -619,6 +662,14 @@ func (m *AclManager) addJumpRulesToRtForward() { Chain: m.chainFwFilter, Exprs: expressions, }) + + if m.workTable6 != nil { + _ = m.rConn.AddRule(&nftables.Rule{ + Table: m.workTable6, + Chain: m.chainFwFilter6, + Exprs: expressions, + }) + } } func (m *AclManager) addMarkAccept() { @@ -652,20 +703,38 @@ func (m *AclManager) addMarkAccept() { Chain: m.chainFwFilter, Exprs: expressions, }) + + if m.workTable6 != nil { + _ = m.rConn.AddRule(&nftables.Rule{ + Table: m.workTable6, + Chain: m.chainFwFilter6, + Exprs: expressions, + }) + } } } -func (m *AclManager) createChain(name string) *nftables.Chain { +func (m *AclManager) createChain(name string) (*nftables.Chain, *nftables.Chain) { chain := &nftables.Chain{ Name: name, Table: m.workTable, } chain = m.rConn.AddChain(chain) - return chain + + var chain6 *nftables.Chain = nil + if m.workTable6 != nil { + chain6 = &nftables.Chain{ + Name: name, + Table: m.workTable6, + } + chain6 = m.rConn.AddChain(chain6) + } + + return chain, chain6 } -func (m *AclManager) createFilterChainWithHook(name string, hookNum nftables.ChainHook) *nftables.Chain { +func (m *AclManager) createFilterChainWithHook(name string, hookNum nftables.ChainHook) (*nftables.Chain, *nftables.Chain) { polAccept := nftables.ChainPolicyAccept chain := &nftables.Chain{ Name: name, @@ -676,10 +745,23 @@ func (m *AclManager) createFilterChainWithHook(name string, hookNum nftables.Cha Policy: &polAccept, } - return m.rConn.AddChain(chain) + var chain6 *nftables.Chain = nil + if m.workTable6 != nil { + chain6 = &nftables.Chain{ + Name: name, + Table: m.workTable6, + Hooknum: hookNum, + Priority: nftables.ChainPriorityFilter, + Type: nftables.ChainTypeFilter, + Policy: &polAccept, + } + chain6 = m.rConn.AddChain(chain6) + } + + return m.rConn.AddChain(chain), chain6 } -func (m *AclManager) createPreroutingMangle() *nftables.Chain { +func (m *AclManager) createPreroutingMangle() (*nftables.Chain, *nftables.Chain) { polAccept := nftables.ChainPolicyAccept chain := &nftables.Chain{ Name: "netbird-acl-prerouting-filter", @@ -745,7 +827,76 @@ func (m *AclManager) createPreroutingMangle() *nftables.Chain { Exprs: expressions, }) chain = m.rConn.AddChain(chain) - return chain + + var chain6 *nftables.Chain = nil + if m.workTable6 != nil { + chain6 = &nftables.Chain{ + Name: "netbird-acl-prerouting-filter", + Table: m.workTable, + Hooknum: nftables.ChainHookPrerouting, + Priority: nftables.ChainPriorityMangle, + Type: nftables.ChainTypeFilter, + Policy: &polAccept, + } + + chain6 = m.rConn.AddChain(chain) + + ip, _ := netip.AddrFromSlice(m.wgIface.Address6().Network.IP.To16()) + expressions := []expr.Any{ + &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: ifname(m.wgIface.Name()), + }, + &expr.Payload{ + DestRegister: 2, + Base: expr.PayloadBaseNetworkHeader, + Offset: 8, + Len: 16, + }, + &expr.Bitwise{ + SourceRegister: 2, + DestRegister: 2, + Len: 16, + Xor: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Mask: m.wgIface.Address6().Network.Mask, + }, + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 2, + Data: ip.Unmap().AsSlice(), + }, + &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseNetworkHeader, + Offset: 24, + Len: 16, + }, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: m.wgIface.Address6().IP.To16(), + }, + &expr.Immediate{ + Register: 1, + Data: postroutingMark, + }, + &expr.Meta{ + Key: expr.MetaKeyMARK, + SourceRegister: true, + Register: 1, + }, + } + _ = m.rConn.AddRule(&nftables.Rule{ + Table: m.workTable6, + Chain: chain, + Exprs: expressions, + }) + chain6 = m.rConn.AddChain(chain6) + } + + return chain, chain6 } func (m *AclManager) addDropExpressions(chain *nftables.Chain, ifaceKey expr.MetaKey) []expr.Any { @@ -759,7 +910,7 @@ func (m *AclManager) addDropExpressions(chain *nftables.Chain, ifaceKey expr.Met &expr.Verdict{Kind: expr.VerdictDrop}, } _ = m.rConn.AddRule(&nftables.Rule{ - Table: m.workTable, + Table: chain.Table, Chain: chain, Exprs: expressions, }) @@ -785,10 +936,45 @@ func (m *AclManager) addJumpRuleToInputChain() { Chain: m.chainFwFilter, Exprs: expressions, }) + + if m.workTable6 != nil { + expressions = []expr.Any{ + &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: ifname(m.wgIface.Name()), + }, + &expr.Verdict{ + Kind: expr.VerdictJump, + Chain: m.chainInputRules6.Name, + }, + } + + _ = m.rConn.AddRule(&nftables.Rule{ + Table: m.workTable6, + Chain: m.chainFwFilter6, + Exprs: expressions, + }) + } } func (m *AclManager) addRouteAllowRule(chain *nftables.Chain, netIfName expr.MetaKey) { ip, _ := netip.AddrFromSlice(m.wgIface.Address().Network.IP.To4()) + addrLen := uint32(4) + srcAddrOffset := uint32(12) + dstAddrOffset := uint32(16) + mask := m.wgIface.Address().Network.Mask + nullArray := []byte{0x0, 0x0, 0x0, 0x0} + if chain.Table.Family == nftables.TableFamilyIPv6 { + ip, _ = netip.AddrFromSlice(m.wgIface.Address6().Network.IP.To16()) + addrLen = 16 + srcAddrOffset = 8 + dstAddrOffset = 24 + mask = m.wgIface.Address6().Network.Mask + nullArray = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} + } + var srcOp, dstOp expr.CmpOp if netIfName == expr.MetaKeyIIFNAME { srcOp = expr.CmpOpEq @@ -807,15 +993,15 @@ func (m *AclManager) addRouteAllowRule(chain *nftables.Chain, netIfName expr.Met &expr.Payload{ DestRegister: 2, Base: expr.PayloadBaseNetworkHeader, - Offset: 12, - Len: 4, + Offset: srcAddrOffset, + Len: addrLen, }, &expr.Bitwise{ SourceRegister: 2, DestRegister: 2, - Len: 4, - Xor: []byte{0x0, 0x0, 0x0, 0x0}, - Mask: m.wgIface.Address().Network.Mask, + Len: addrLen, + Xor: nullArray, + Mask: mask, }, &expr.Cmp{ Op: srcOp, @@ -825,15 +1011,15 @@ func (m *AclManager) addRouteAllowRule(chain *nftables.Chain, netIfName expr.Met &expr.Payload{ DestRegister: 2, Base: expr.PayloadBaseNetworkHeader, - Offset: 16, - Len: 4, + Offset: dstAddrOffset, + Len: addrLen, }, &expr.Bitwise{ SourceRegister: 2, DestRegister: 2, - Len: 4, - Xor: []byte{0x0, 0x0, 0x0, 0x0}, - Mask: m.wgIface.Address().Network.Mask, + Len: addrLen, + Xor: nullArray, + Mask: mask, }, &expr.Cmp{ Op: dstOp, @@ -853,6 +1039,20 @@ func (m *AclManager) addRouteAllowRule(chain *nftables.Chain, netIfName expr.Met func (m *AclManager) addFwdAllow(chain *nftables.Chain, iifname expr.MetaKey) { ip, _ := netip.AddrFromSlice(m.wgIface.Address().Network.IP.To4()) + addrLen := uint32(4) + srcAddrOffset := uint32(12) + dstAddrOffset := uint32(16) + mask := m.wgIface.Address().Network.Mask + nullArray := []byte{0x0, 0x0, 0x0, 0x0} + if chain.Table.Family == nftables.TableFamilyIPv6 { + ip, _ = netip.AddrFromSlice(m.wgIface.Address6().Network.IP.To16()) + addrLen = 16 + srcAddrOffset = 8 + dstAddrOffset = 24 + mask = m.wgIface.Address6().Network.Mask + nullArray = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} + } + var srcOp, dstOp expr.CmpOp if iifname == expr.MetaKeyIIFNAME { srcOp = expr.CmpOpNeq @@ -871,15 +1071,15 @@ func (m *AclManager) addFwdAllow(chain *nftables.Chain, iifname expr.MetaKey) { &expr.Payload{ DestRegister: 2, Base: expr.PayloadBaseNetworkHeader, - Offset: 12, - Len: 4, + Offset: srcAddrOffset, + Len: addrLen, }, &expr.Bitwise{ SourceRegister: 2, DestRegister: 2, - Len: 4, - Xor: []byte{0x0, 0x0, 0x0, 0x0}, - Mask: m.wgIface.Address().Network.Mask, + Len: addrLen, + Xor: nullArray, + Mask: mask, }, &expr.Cmp{ Op: srcOp, @@ -889,15 +1089,15 @@ func (m *AclManager) addFwdAllow(chain *nftables.Chain, iifname expr.MetaKey) { &expr.Payload{ DestRegister: 2, Base: expr.PayloadBaseNetworkHeader, - Offset: 16, - Len: 4, + Offset: dstAddrOffset, + Len: addrLen, }, &expr.Bitwise{ SourceRegister: 2, DestRegister: 2, - Len: 4, - Xor: []byte{0x0, 0x0, 0x0, 0x0}, - Mask: m.wgIface.Address().Network.Mask, + Len: addrLen, + Xor: nullArray, + Mask: mask, }, &expr.Cmp{ Op: dstOp, @@ -917,6 +1117,20 @@ func (m *AclManager) addFwdAllow(chain *nftables.Chain, iifname expr.MetaKey) { func (m *AclManager) addJumpRule(chain *nftables.Chain, to string, ifaceKey expr.MetaKey) { ip, _ := netip.AddrFromSlice(m.wgIface.Address().Network.IP.To4()) + addrLen := uint32(4) + srcAddrOffset := uint32(12) + dstAddrOffset := uint32(16) + mask := m.wgIface.Address().Network.Mask + nullArray := []byte{0x0, 0x0, 0x0, 0x0} + if chain.Table.Family == nftables.TableFamilyIPv6 { + ip, _ = netip.AddrFromSlice(m.wgIface.Address6().Network.IP.To16()) + addrLen = 16 + srcAddrOffset = 8 + dstAddrOffset = 24 + mask = m.wgIface.Address6().Network.Mask + nullArray = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} + } + expressions := []expr.Any{ &expr.Meta{Key: ifaceKey, Register: 1}, &expr.Cmp{ @@ -927,15 +1141,15 @@ func (m *AclManager) addJumpRule(chain *nftables.Chain, to string, ifaceKey expr &expr.Payload{ DestRegister: 2, Base: expr.PayloadBaseNetworkHeader, - Offset: 12, - Len: 4, + Offset: srcAddrOffset, + Len: addrLen, }, &expr.Bitwise{ SourceRegister: 2, DestRegister: 2, - Len: 4, - Xor: []byte{0x0, 0x0, 0x0, 0x0}, - Mask: m.wgIface.Address().Network.Mask, + Len: addrLen, + Xor: nullArray, + Mask: mask, }, &expr.Cmp{ Op: expr.CmpOpEq, @@ -945,15 +1159,15 @@ func (m *AclManager) addJumpRule(chain *nftables.Chain, to string, ifaceKey expr &expr.Payload{ DestRegister: 2, Base: expr.PayloadBaseNetworkHeader, - Offset: 16, - Len: 4, + Offset: dstAddrOffset, + Len: addrLen, }, &expr.Bitwise{ SourceRegister: 2, DestRegister: 2, - Len: 4, - Xor: []byte{0x0, 0x0, 0x0, 0x0}, - Mask: m.wgIface.Address().Network.Mask, + Len: addrLen, + Xor: nullArray, + Mask: mask, }, &expr.Cmp{ Op: expr.CmpOpEq, @@ -975,38 +1189,67 @@ func (m *AclManager) addJumpRule(chain *nftables.Chain, to string, ifaceKey expr func (m *AclManager) addIpToSet(ipsetName string, ip net.IP) (*nftables.Set, error) { ipset, err := m.rConn.GetSetByName(m.workTable, ipsetName) rawIP := ip.To4() - if err != nil { - if ipset, err = m.createSet(m.workTable, ipsetName); err != nil { - return nil, fmt.Errorf("get set name: %v", err) + ipsetType := nftables.TypeIPAddr + if rawIP == nil { + rawIP = ip.To16() + ipsetType = nftables.TypeIP6Addr + } + if ipsetType == nftables.TypeIPAddr { + if err != nil { + if ipset, err = m.createSet(m.workTable, ipsetName, ipsetType); err != nil { + return nil, fmt.Errorf("get set name: %v", err) + } + + m.ipsetStore.newIpset(ipset.Name) } - m.ipsetStore.newIpset(ipset.Name) - } + if m.ipsetStore.IsIpInSet(ipset.Name, ip) { + return ipset, nil + } - if m.ipsetStore.IsIpInSet(ipset.Name, ip) { - return ipset, nil - } + if err := m.sConn.SetAddElements(ipset, []nftables.SetElement{{Key: rawIP}}); err != nil { + return nil, fmt.Errorf("add set element for the first time: %v", err) + } - if err := m.sConn.SetAddElements(ipset, []nftables.SetElement{{Key: rawIP}}); err != nil { - return nil, fmt.Errorf("add set element for the first time: %v", err) - } + m.ipsetStore.AddIpToSet(ipset.Name, ip) - m.ipsetStore.AddIpToSet(ipset.Name, ip) + if err := m.sConn.Flush(); err != nil { + return nil, fmt.Errorf("flush add elements: %v", err) + } + } else { + if err != nil { + if ipset, err = m.createSet(m.workTable6, ipsetName, ipsetType); err != nil { + return nil, fmt.Errorf("get set name: %v", err) + } - if err := m.sConn.Flush(); err != nil { - return nil, fmt.Errorf("flush add elements: %v", err) + m.ipsetStore6.newIpset(ipset.Name) + } + + if m.ipsetStore6.IsIpInSet(ipset.Name, ip) { + return ipset, nil + } + + if err := m.sConn.SetAddElements(ipset, []nftables.SetElement{{Key: rawIP}}); err != nil { + return nil, fmt.Errorf("add set element for the first time: %v", err) + } + + m.ipsetStore6.AddIpToSet(ipset.Name, ip) + + if err := m.sConn.Flush(); err != nil { + return nil, fmt.Errorf("flush add elements: %v", err) + } } return ipset, nil } // createSet in given table by name -func (m *AclManager) createSet(table *nftables.Table, name string) (*nftables.Set, error) { +func (m *AclManager) createSet(table *nftables.Table, name string, ipsetType nftables.SetDatatype) (*nftables.Set, error) { ipset := &nftables.Set{ Name: name, Table: table, Dynamic: true, - KeyType: nftables.TypeIPAddr, + KeyType: ipsetType, } if err := m.rConn.AddSet(ipset, nil); err != nil { diff --git a/client/firewall/nftables/manager_linux.go b/client/firewall/nftables/manager_linux.go index fad2d78049d..4519a8a6908 100644 --- a/client/firewall/nftables/manager_linux.go +++ b/client/firewall/nftables/manager_linux.go @@ -36,17 +36,17 @@ func Create(context context.Context, wgIface iFaceMapper) (*Manager, error) { wgIface: wgIface, } - workTable, err := m.createWorkTable() + workTable, workTable6, err := m.createWorkTables() if err != nil { return nil, err } - m.router, err = newRouter(context, workTable) + m.router, err = newRouter(context, workTable, workTable6) if err != nil { return nil, err } - m.aclManager, err = newAclManager(workTable, wgIface, m.router.RouteingFwChainName()) + m.aclManager, err = newAclManager(workTable, workTable6, wgIface, m.router.RouteingFwChainName()) if err != nil { return nil, err } @@ -71,10 +71,10 @@ func (m *Manager) AddFiltering( m.mutex.Lock() defer m.mutex.Unlock() - rawIP := ip.To4() - if rawIP == nil { - return nil, fmt.Errorf("unsupported IP version: %s", ip.String()) - } + //rawIP := ip.To4() + //if rawIP == nil { + // return nil, fmt.Errorf("unsupported IP version: %s", ip.String()) + //} return m.aclManager.AddFiltering(ip, proto, sPort, dPort, direction, action, ipsetName, comment) } @@ -202,21 +202,26 @@ func (m *Manager) Flush() error { return m.aclManager.Flush() } -func (m *Manager) createWorkTable() (*nftables.Table, error) { +func (m *Manager) createWorkTables() (*nftables.Table, *nftables.Table, error) { tables, err := m.rConn.ListTablesOfFamily(nftables.TableFamilyIPv4) if err != nil { - return nil, fmt.Errorf("list of tables: %w", err) + return nil, nil, fmt.Errorf("list of tables: %w", err) + } + tables6, err := m.rConn.ListTablesOfFamily(nftables.TableFamilyIPv6) + if err != nil { + return nil, nil, fmt.Errorf("list of v6 tables: %w", err) } - for _, t := range tables { + for _, t := range append(tables, tables6...) { if t.Name == tableName { m.rConn.DelTable(t) } } table := m.rConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv4}) + table6 := m.rConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv6}) err = m.rConn.Flush() - return table, err + return table, table6, err } func (m *Manager) applyAllowNetbirdRules(chain *nftables.Chain) { diff --git a/client/firewall/nftables/route_linux.go b/client/firewall/nftables/route_linux.go index 381136e50b7..a1a7e0aef24 100644 --- a/client/firewall/nftables/route_linux.go +++ b/client/firewall/nftables/route_linux.go @@ -24,9 +24,12 @@ const ( userDataAcceptForwardRuleDst = "frwacceptdst" ) +// TODO ipv6 everywhere here. + // some presets for building nftable rules var ( - zeroXor = binaryutil.NativeEndian.PutUint32(0) + zeroXor = binaryutil.NativeEndian.PutUint32(0) + zeroXor6 = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} exprCounterAccept = []expr.Any{ &expr.Counter{}, @@ -39,34 +42,39 @@ var ( ) type router struct { - ctx context.Context - stop context.CancelFunc - conn *nftables.Conn - workTable *nftables.Table - filterTable *nftables.Table - chains map[string]*nftables.Chain + ctx context.Context + stop context.CancelFunc + conn *nftables.Conn + workTable *nftables.Table + workTable6 *nftables.Table + filterTable *nftables.Table + filterTable6 *nftables.Table + chains map[string]*nftables.Chain + chains6 map[string]*nftables.Chain // rules is useful to avoid duplicates and to get missing attributes that we don't have when adding new rules rules map[string]*nftables.Rule isDefaultFwdRulesEnabled bool } -func newRouter(parentCtx context.Context, workTable *nftables.Table) (*router, error) { +func newRouter(parentCtx context.Context, workTable *nftables.Table, workTable6 *nftables.Table) (*router, error) { ctx, cancel := context.WithCancel(parentCtx) r := &router{ - ctx: ctx, - stop: cancel, - conn: &nftables.Conn{}, - workTable: workTable, - chains: make(map[string]*nftables.Chain), - rules: make(map[string]*nftables.Rule), + ctx: ctx, + stop: cancel, + conn: &nftables.Conn{}, + workTable: workTable, + workTable6: workTable6, + chains: make(map[string]*nftables.Chain), + chains6: make(map[string]*nftables.Chain), + rules: make(map[string]*nftables.Rule), } var err error - r.filterTable, err = r.loadFilterTable() + r.filterTable, r.filterTable6, err = r.loadFilterTables(workTable6 != nil) if err != nil { if errors.Is(err, errFilterTableNotFound) { - log.Warnf("table 'filter' not found for forward rules") + log.Warnf("table 'filter' not found for forward rules for one of the supported address families-") } else { return nil, err } @@ -96,19 +104,40 @@ func (r *router) ResetForwardRules() { } } -func (r *router) loadFilterTable() (*nftables.Table, error) { +func (r *router) loadFilterTables(include6 bool) (*nftables.Table, *nftables.Table, error) { tables, err := r.conn.ListTablesOfFamily(nftables.TableFamilyIPv4) if err != nil { - return nil, fmt.Errorf("nftables: unable to list tables: %v", err) + return nil, nil, fmt.Errorf("nftables: unable to list tables: %v", err) } + var table4 *nftables.Table = nil for _, table := range tables { if table.Name == "filter" { - return table, nil + table4 = table + break + } + } + + var table6 *nftables.Table = nil + if include6 { + tables, err = r.conn.ListTablesOfFamily(nftables.TableFamilyIPv6) + if err != nil { + return nil, nil, fmt.Errorf("nftables: unable to list tables: %v", err) } + for _, table := range tables { + if table.Name == "filter" { + table6 = table + break + } + } + } + + err = nil + if table4 == nil || table6 == nil { + err = errFilterTableNotFound } - return nil, errFilterTableNotFound + return table4, table6, errFilterTableNotFound } func (r *router) createContainers() error { @@ -126,6 +155,21 @@ func (r *router) createContainers() error { Type: nftables.ChainTypeNAT, }) + if r.workTable6 != nil { + r.chains6[chainNameRouteingFw] = r.conn.AddChain(&nftables.Chain{ + Name: chainNameRouteingFw, + Table: r.workTable6, + }) + + r.chains6[chainNameRoutingNat] = r.conn.AddChain(&nftables.Chain{ + Name: chainNameRoutingNat, + Table: r.workTable6, + Hooknum: nftables.ChainHookPostrouting, + Priority: nftables.ChainPriorityNATSource - 1, + Type: nftables.ChainTypeNAT, + }) + } + err := r.refreshRulesMap() if err != nil { log.Errorf("failed to clean up rules from FORWARD chain: %s", err) @@ -165,7 +209,12 @@ func (r *router) InsertRoutingRules(pair manager.RouterPair) error { } } - if r.filterTable != nil && !r.isDefaultFwdRulesEnabled { + filterTable := r.filterTable + parsedIp, _, _ := net.ParseCIDR(pair.Source) + if parsedIp.To4() == nil { + filterTable = r.filterTable6 + } + if filterTable != nil && !r.isDefaultFwdRulesEnabled { log.Debugf("add default accept forward rule") r.acceptForwardRule(pair.Source) } @@ -199,9 +248,15 @@ func (r *router) insertRoutingRule(format, chainName string, pair manager.Router } } + table, chain := r.workTable, r.chains[chainName] + parsedIp, _, _ := net.ParseCIDR(pair.Source) + if parsedIp.To4() == nil { + table, chain = r.workTable6, r.chains6[chainName] + } + r.rules[ruleKey] = r.conn.InsertRule(&nftables.Rule{ - Table: r.workTable, - Chain: r.chains[chainName], + Table: table, + Chain: chain, Exprs: expression, UserData: []byte(ruleKey), }) @@ -211,6 +266,12 @@ func (r *router) insertRoutingRule(format, chainName string, pair manager.Router func (r *router) acceptForwardRule(sourceNetwork string) { src := generateCIDRMatcherExpressions(true, sourceNetwork) dst := generateCIDRMatcherExpressions(false, "0.0.0.0/0") + table := r.filterTable + parsedIp, _, _ := net.ParseCIDR(sourceNetwork) + if parsedIp.To4() == nil { + dst = generateCIDRMatcherExpressions(false, "::/0") + table = r.filterTable6 + } var exprs []expr.Any exprs = append(src, append(dst, &expr.Verdict{ // nolint:gocritic @@ -221,7 +282,7 @@ func (r *router) acceptForwardRule(sourceNetwork string) { Table: r.filterTable, Chain: &nftables.Chain{ Name: "FORWARD", - Table: r.filterTable, + Table: table, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, @@ -233,6 +294,9 @@ func (r *router) acceptForwardRule(sourceNetwork string) { r.conn.AddRule(rule) src = generateCIDRMatcherExpressions(true, "0.0.0.0/0") + if parsedIp.To4() == nil { + src = generateCIDRMatcherExpressions(true, "::/0") + } dst = generateCIDRMatcherExpressions(false, sourceNetwork) exprs = append(src, append(dst, &expr.Verdict{ //nolint:gocritic @@ -243,7 +307,7 @@ func (r *router) acceptForwardRule(sourceNetwork string) { Table: r.filterTable, Chain: &nftables.Chain{ Name: "FORWARD", - Table: r.filterTable, + Table: table, Type: nftables.ChainTypeFilter, Hooknum: nftables.ChainHookForward, Priority: nftables.ChainPriorityFilter, @@ -363,6 +427,29 @@ func (r *router) cleanUpDefaultForwardRules() error { } } + if r.filterTable6 != nil { + + chains, err = r.conn.ListChainsOfTableFamily(nftables.TableFamilyIPv6) + if err != nil { + return err + } + + for _, chain := range chains { + if chain.Table.Name != r.filterTable6.Name { + continue + } + if chain.Name != "FORWARD" { + continue + } + + rules6, err := r.conn.GetRules(r.filterTable, chain) + if err != nil { + return err + } + rules6 = append(rules, rules6...) + } + } + for _, rule := range rules { if bytes.Equal(rule.UserData, []byte(userDataAcceptForwardRuleSrc)) || bytes.Equal(rule.UserData, []byte(userDataAcceptForwardRuleDst)) { err := r.conn.DelRule(rule) @@ -387,6 +474,18 @@ func generateCIDRMatcherExpressions(source bool, cidr string) []expr.Any { } else { offSet = 16 // dst offset } + addrLen := uint32(4) + zeroXor := zeroXor + + if ip.To4() == nil { + if source { + offSet = 8 // src offset + } else { + offSet = 24 // dst offset + } + addrLen = 16 + zeroXor = zeroXor6 + } return []expr.Any{ // fetch src add @@ -394,13 +493,13 @@ func generateCIDRMatcherExpressions(source bool, cidr string) []expr.Any { DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: offSet, - Len: 4, + Len: addrLen, }, // net mask &expr.Bitwise{ DestRegister: 1, SourceRegister: 1, - Len: 4, + Len: addrLen, Mask: network.Mask, Xor: zeroXor, }, diff --git a/client/internal/routemanager/server_nonandroid.go b/client/internal/routemanager/server_nonandroid.go index 20e500e7944..94e0a823b83 100644 --- a/client/internal/routemanager/server_nonandroid.go +++ b/client/internal/routemanager/server_nonandroid.go @@ -4,6 +4,7 @@ package routemanager import ( "context" + "fmt" "net/netip" "sync" @@ -92,7 +93,7 @@ func (m *defaultServerRouter) removeFromServerNetwork(route *route.Route) error } } -func (m *defaultServerRouter) addToServerNetwork(route *route.Route) error { +func (m *defaultServerRouter) addToServerNetwork(rt *route.Route) error { select { case <-m.ctx.Done(): log.Infof("not adding to server network because context is done") @@ -100,11 +101,18 @@ func (m *defaultServerRouter) addToServerNetwork(route *route.Route) error { default: m.mux.Lock() defer m.mux.Unlock() - err := m.firewall.InsertRoutingRules(routeToRouterPair(m.wgInterface.Address().String(), route)) + routingAddress := m.wgInterface.Address().String() + if rt.NetworkType == route.IPv6Network { + if m.wgInterface.Address6() == nil { + return fmt.Errorf("attempted to add route for IPv6 even though device has no v6 address") + } + routingAddress = m.wgInterface.Address6().String() + } + err := m.firewall.InsertRoutingRules(routeToRouterPair(routingAddress, rt)) if err != nil { return err } - m.routes[route.ID] = route + m.routes[rt.ID] = rt return nil } } @@ -113,7 +121,14 @@ func (m *defaultServerRouter) cleanUp() { m.mux.Lock() defer m.mux.Unlock() for _, r := range m.routes { - err := m.firewall.RemoveRoutingRules(routeToRouterPair(m.wgInterface.Address().String(), r)) + routingAddress := m.wgInterface.Address().String() + if r.NetworkType == route.IPv6Network { + if m.wgInterface.Address6() == nil { + log.Errorf("attempted to remove route for IPv6 even though device has no v6 address") + } + routingAddress = m.wgInterface.Address6().String() + } + err := m.firewall.RemoveRoutingRules(routeToRouterPair(routingAddress, r)) if err != nil { log.Warnf("failed to remove clean up route: %s", r.ID) } diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go index 0529aaad0fb..2c959369178 100644 --- a/management/server/http/peers_handler.go +++ b/management/server/http/peers_handler.go @@ -276,6 +276,8 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn Hostname: peer.Meta.Hostname, UserId: &peer.UserID, UiVersion: &peer.Meta.UIVersion, + Ipv6Supported: peer.Meta.Ipv6Supported, + Ipv6Enabled: peer.IP6 != nil, DnsLabel: fqdn(peer, dnsDomain), LoginExpirationEnabled: peer.LoginExpirationEnabled, LastLogin: peer.LastLogin, From 45d20d201c4c312ba734dcd05b2e14ed2297a0a6 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Sun, 24 Dec 2023 22:37:25 +0100 Subject: [PATCH 07/42] Fix peer list item response not containing IPv6 address --- management/proto/management.pb.go | 2 +- management/server/http/peers_handler.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/management/proto/management.pb.go b/management/proto/management.pb.go index beae9b56b12..061a54e84e8 100644 --- a/management/proto/management.pb.go +++ b/management/proto/management.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v4.25.0 +// protoc v4.25.1 // source: management.proto package proto diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go index 2c959369178..179b907e99d 100644 --- a/management/server/http/peers_handler.go +++ b/management/server/http/peers_handler.go @@ -263,10 +263,16 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD } func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeersCount int) *api.PeerBatch { + var ip6 *string + if peer.IP6 != nil { + ip6string := peer.IP6.String() + ip6 = &ip6string + } return &api.PeerBatch{ Id: peer.ID, Name: peer.Name, Ip: peer.IP.String(), + Ip6: ip6, Connected: peer.Status.Connected, LastSeen: peer.Status.LastSeen, Os: fmt.Sprintf("%s %s", peer.Meta.OS, peer.Meta.Core), From f6b8284cf7c0e29cde05b061bdc3a578e77a3c8d Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Fri, 12 Jan 2024 03:13:11 +0100 Subject: [PATCH 08/42] Fix nftables breaking on IPv6 address change --- client/firewall/iptables/manager_linux.go | 4 + client/firewall/manager/firewall.go | 4 + client/firewall/nftables/acl_linux.go | 401 +++++++++++----------- client/firewall/nftables/manager_linux.go | 11 + client/firewall/nftables/route_linux.go | 115 +++++-- client/firewall/uspfilter/uspfilter.go | 4 + client/internal/acl/manager.go | 143 ++++---- client/internal/engine.go | 5 + client/internal/routemanager/client.go | 15 +- management/server/grpcserver.go | 24 +- 10 files changed, 413 insertions(+), 313 deletions(-) diff --git a/client/firewall/iptables/manager_linux.go b/client/firewall/iptables/manager_linux.go index 2d231ec456d..74f1c62a9c1 100644 --- a/client/firewall/iptables/manager_linux.go +++ b/client/firewall/iptables/manager_linux.go @@ -24,6 +24,10 @@ type Manager struct { router *routerManager } +func (m *Manager) ResetV6RulesAndAddr() error { + return nil +} + // iFaceMapper defines subset methods of interface required for manager type iFaceMapper interface { Name() string diff --git a/client/firewall/manager/firewall.go b/client/firewall/manager/firewall.go index 6e4edb63e7c..d4989c7209c 100644 --- a/client/firewall/manager/firewall.go +++ b/client/firewall/manager/firewall.go @@ -76,6 +76,10 @@ type Manager interface { // RemoveRoutingRules removes a routing firewall rule RemoveRoutingRules(pair RouterPair) error + // UpdateV6Address makes changes to the firewall to adapt to the IP address changes. + // It is expected that after calling this method ApplyFiltering will be called to re-add the firewall rules. + ResetV6RulesAndAddr() error + // Reset firewall to the default state Reset() error diff --git a/client/firewall/nftables/acl_linux.go b/client/firewall/nftables/acl_linux.go index 4e8a6a9a941..3e2e31f4ca6 100644 --- a/client/firewall/nftables/acl_linux.go +++ b/client/firewall/nftables/acl_linux.go @@ -30,6 +30,8 @@ const ( chainNameOutputFilter = "netbird-acl-output-filter" chainNameForwardFilter = "netbird-acl-forward-filter" + setNameHostIpAddrs = "netbird-acl-host-ips" + allowNetbirdInputRuleID = "allow Netbird incoming traffic" ) @@ -46,6 +48,7 @@ type AclManager struct { workTable *nftables.Table workTable6 *nftables.Table + v6Active bool chainInputRules *nftables.Chain chainOutputRules *nftables.Chain chainFwFilter *nftables.Chain @@ -83,6 +86,7 @@ func newAclManager(table *nftables.Table, table6 *nftables.Table, wgIface iFaceM wgIface: wgIface, workTable: table, workTable6: table6, + v6Active: wgIface.Address6() != nil, routeingFwChainName: routeingFwChainName, ipsetStore: newIpsetStore(), @@ -95,9 +99,46 @@ func newAclManager(table *nftables.Table, table6 *nftables.Table, wgIface iFaceM return nil, err } + if m.v6Active { + err = m.createDefaultChains6() + if err != nil { + return nil, err + } + } + return m, nil } +// Resets the IPv6 Firewall Table to adapt to changes in IP addresses +func (m *AclManager) UpdateV6Address() error { + for k, r := range m.rules { + if r.ip.To4() == nil { + err := m.DeleteRule(r) + if err != nil { + return err + } + delete(m.rules, k) + } + } + sets, err := m.rConn.GetSets(m.workTable6) + if err != nil { + for _, set := range sets { + m.rConn.DelSet(set) + } + } + m.ipsetStore6 = newIpsetStore() + m.rConn.FlushTable(m.workTable6) + if m.wgIface.Address6() != nil { + err := m.createDefaultChains6() + if err != nil { + return err + } + } + m.v6Active = m.wgIface.Address6() != nil + + return nil +} + // AddFiltering rule to the firewall // // If comment argument is empty firewall manager should set @@ -120,6 +161,9 @@ func (m *AclManager) AddFiltering( return nil, err } } + if !m.v6Active && ip.To4() == nil { + return nil, fmt.Errorf("attempted to configure filtering for IPv6 address even though IPv6 is not active") + } newRules := make([]firewall.Rule, 0, 2) ioRule, err := m.addIOFiltering(ip, proto, sPort, dPort, direction, action, ipset, comment) @@ -219,18 +263,30 @@ func (m *AclManager) Flush() error { return err } - if err := m.refreshRuleHandles(m.chainInputRules); err != nil { + if err := m.refreshRuleHandles(m.workTable, m.chainInputRules); err != nil { log.Errorf("failed to refresh rule handles ipv4 input chain: %v", err) } - if err := m.refreshRuleHandles(m.chainOutputRules); err != nil { + if err := m.refreshRuleHandles(m.workTable, m.chainOutputRules); err != nil { log.Errorf("failed to refresh rule handles IPv4 output chain: %v", err) } - if err := m.refreshRuleHandles(m.chainPrerouting); err != nil { + if err := m.refreshRuleHandles(m.workTable, m.chainPrerouting); err != nil { log.Errorf("failed to refresh rule handles IPv4 prerouting chain: %v", err) } + if err := m.refreshRuleHandles(m.workTable6, m.chainInputRules6); err != nil { + log.Errorf("failed to refresh rule handles ipv6 input chain: %v", err) + } + + if err := m.refreshRuleHandles(m.workTable6, m.chainOutputRules6); err != nil { + log.Errorf("failed to refresh rule handles IPv6 output chain: %v", err) + } + + if err := m.refreshRuleHandles(m.workTable6, m.chainPrerouting6); err != nil { + log.Errorf("failed to refresh rule handles IPv6 prerouting chain: %v", err) + } + return nil } @@ -283,8 +339,10 @@ func (m *AclManager) addIOFiltering(ip net.IP, proto firewall.Protocol, sPort *f } rawIP := ip.To4() + table := m.workTable if rawIP == nil { rawIP = ip.To16() + table = m.workTable6 } // check if rawIP contains zeroed IP address value // in that case not add IP match expression into the rule definition @@ -376,7 +434,7 @@ func (m *AclManager) addIOFiltering(ip net.IP, proto firewall.Protocol, sPort *f chain = m.chainOutputRules } nftRule := m.rConn.InsertRule(&nftables.Rule{ - Table: m.workTable, + Table: table, Chain: chain, Position: 0, Exprs: expressions, @@ -539,53 +597,39 @@ func (m *AclManager) addPreroutingFiltering(ipset *nftables.Set, proto firewall. func (m *AclManager) createDefaultChains() (err error) { // chainNameInputRules - chain, chain6 := m.createChain(chainNameInputRules) + chain := m.createChain(chainNameInputRules, m.workTable) err = m.rConn.Flush() if err != nil { log.Debugf("failed to create chain (%s): %s", chain.Name, err) return err } m.chainInputRules = chain - m.chainInputRules6 = chain6 // chainNameOutputRules - chain, chain6 = m.createChain(chainNameOutputRules) + chain = m.createChain(chainNameOutputRules, m.workTable) err = m.rConn.Flush() if err != nil { log.Debugf("failed to create chain (%s): %s", chainNameOutputRules, err) return err } m.chainOutputRules = chain - m.chainOutputRules6 = chain6 // netbird-acl-input-filter // type filter hook input priority filter; policy accept; - chain, chain6 = m.createFilterChainWithHook(chainNameInputFilter, nftables.ChainHookInput) + chain = m.createFilterChainWithHook(chainNameInputFilter, nftables.ChainHookInput, m.workTable) //netbird-acl-input-filter iifname "wt0" ip saddr 100.72.0.0/16 ip daddr != 100.72.0.0/16 accept m.addRouteAllowRule(chain, expr.MetaKeyIIFNAME) m.addFwdAllow(chain, expr.MetaKeyIIFNAME) m.addJumpRule(chain, m.chainInputRules.Name, expr.MetaKeyIIFNAME) // to netbird-acl-input-rules m.addDropExpressions(chain, expr.MetaKeyIIFNAME) - if chain6 != nil { - m.addRouteAllowRule(chain6, expr.MetaKeyIIFNAME) - m.addFwdAllow(chain6, expr.MetaKeyIIFNAME) - m.addJumpRule(chain6, m.chainInputRules6.Name, expr.MetaKeyIIFNAME) // to netbird-acl-input-rules - m.addDropExpressions(chain6, expr.MetaKeyIIFNAME) - } // netbird-acl-output-filter // type filter hook output priority filter; policy accept; - chain, chain6 = m.createFilterChainWithHook(chainNameOutputFilter, nftables.ChainHookOutput) + chain = m.createFilterChainWithHook(chainNameOutputFilter, nftables.ChainHookOutput, m.workTable) m.addRouteAllowRule(chain, expr.MetaKeyOIFNAME) m.addFwdAllow(chain, expr.MetaKeyOIFNAME) m.addJumpRule(chain, m.chainOutputRules.Name, expr.MetaKeyOIFNAME) // to netbird-acl-output-rules m.addDropExpressions(chain, expr.MetaKeyOIFNAME) - if chain6 != nil { - m.addRouteAllowRule(chain6, expr.MetaKeyOIFNAME) - m.addFwdAllow(chain6, expr.MetaKeyOIFNAME) - m.addJumpRule(chain6, m.chainOutputRules6.Name, expr.MetaKeyOIFNAME) // to netbird-acl-output-rules - m.addDropExpressions(chain6, expr.MetaKeyOIFNAME) - } err = m.rConn.Flush() if err != nil { log.Debugf("failed to create chain (%s): %s", chainNameOutputFilter, err) @@ -593,10 +637,10 @@ func (m *AclManager) createDefaultChains() (err error) { } // netbird-acl-forward-filter - m.chainFwFilter, m.chainFwFilter6 = m.createFilterChainWithHook(chainNameForwardFilter, nftables.ChainHookForward) - m.addJumpRulesToRtForward() // to - m.addMarkAccept() - m.addJumpRuleToInputChain() // to netbird-acl-input-rules + m.chainFwFilter = m.createFilterChainWithHook(chainNameForwardFilter, nftables.ChainHookForward, m.workTable) + m.addJumpRulesToRtForward(m.workTable, m.chainFwFilter) // to + m.addMarkAccept(m.workTable, m.chainFwFilter) + m.addJumpRuleToInputChain(m.workTable, m.chainFwFilter, m.chainInputRules) // to netbird-acl-input-rules m.addDropExpressions(m.chainFwFilter, expr.MetaKeyIIFNAME) err = m.rConn.Flush() if err != nil { @@ -606,7 +650,7 @@ func (m *AclManager) createDefaultChains() (err error) { // netbird-acl-output-filter // type filter hook output priority filter; policy accept; - m.chainPrerouting, m.chainPrerouting6 = m.createPreroutingMangle() + m.chainPrerouting = m.createPreroutingMangle(m.workTable, false) err = m.rConn.Flush() if err != nil { log.Debugf("failed to create chain (%s): %s", m.chainPrerouting.Name, err) @@ -615,7 +659,73 @@ func (m *AclManager) createDefaultChains() (err error) { return nil } -func (m *AclManager) addJumpRulesToRtForward() { +func (m *AclManager) createDefaultChains6() (err error) { + + // chainNameInputRules + chain := m.createChain(chainNameInputRules, m.workTable6) + err = m.rConn.Flush() + if err != nil { + log.Debugf("failed to create chain (%s): %s", chain.Name, err) + return err + } + m.chainInputRules6 = chain + + // chainNameOutputRules + chain = m.createChain(chainNameOutputRules, m.workTable6) + err = m.rConn.Flush() + if err != nil { + log.Debugf("failed to create chain (%s): %s", chainNameOutputRules, err) + return err + } + m.chainOutputRules6 = chain + + // netbird-acl-input-filter + // type filter hook input priority filter; policy accept; + chain = m.createFilterChainWithHook(chainNameInputFilter, nftables.ChainHookInput, m.workTable6) + //netbird-acl-input-filter iifname "wt0" ip saddr 100.72.0.0/16 ip daddr != 100.72.0.0/16 accept + m.addRouteAllowRule(chain, expr.MetaKeyIIFNAME) + m.addFwdAllow(chain, expr.MetaKeyIIFNAME) + m.addJumpRule(chain, m.chainInputRules6.Name, expr.MetaKeyIIFNAME) // to netbird-acl-input-rules + m.addDropExpressions(chain, expr.MetaKeyIIFNAME) + + // netbird-acl-output-filter + // type filter hook output priority filter; policy accept; + chain = m.createFilterChainWithHook(chainNameOutputFilter, nftables.ChainHookOutput, m.workTable6) + m.addRouteAllowRule(chain, expr.MetaKeyOIFNAME) + m.addFwdAllow(chain, expr.MetaKeyOIFNAME) + m.addJumpRule(chain, m.chainOutputRules6.Name, expr.MetaKeyOIFNAME) // to netbird-acl-output-rules + m.addDropExpressions(chain, expr.MetaKeyOIFNAME) + err = m.rConn.Flush() + if err != nil { + log.Debugf("failed to create chain (%s): %s", chainNameOutputFilter, err) + return err + } + + // netbird-acl-forward-filter + m.chainFwFilter6 = m.createFilterChainWithHook(chainNameForwardFilter, nftables.ChainHookForward, m.workTable6) + m.addJumpRulesToRtForward(m.workTable6, m.chainFwFilter6) // to + m.addMarkAccept(m.workTable6, m.chainFwFilter6) + m.addJumpRuleToInputChain(m.workTable6, m.chainFwFilter6, m.chainInputRules6) // to netbird-acl-input-rules + m.addDropExpressions(m.chainFwFilter6, expr.MetaKeyIIFNAME) + err = m.rConn.Flush() + if err != nil { + log.Debugf("failed to create chain (%s): %s", chainNameForwardFilter, err) + return err + } + + // netbird-acl-output-filter + // type filter hook output priority filter; policy accept; + m.chainPrerouting6 = m.createPreroutingMangle(m.workTable6, true) + err = m.rConn.Flush() + if err != nil { + log.Debugf("failed to create chain (%s): %s", m.chainPrerouting.Name, err) + return err + } + return nil +} + +func (m *AclManager) addJumpRulesToRtForward(table *nftables.Table, chain *nftables.Chain) { + expressions := []expr.Any{ &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, &expr.Cmp{ @@ -630,20 +740,11 @@ func (m *AclManager) addJumpRulesToRtForward() { } _ = m.rConn.AddRule(&nftables.Rule{ - Table: m.workTable, - Chain: m.chainFwFilter, + Table: table, + Chain: chain, Exprs: expressions, }) - if m.workTable6 != nil { - - _ = m.rConn.AddRule(&nftables.Rule{ - Table: m.workTable6, - Chain: m.chainFwFilter6, - Exprs: expressions, - }) - } - expressions = []expr.Any{ &expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1}, &expr.Cmp{ @@ -658,21 +759,13 @@ func (m *AclManager) addJumpRulesToRtForward() { } _ = m.rConn.AddRule(&nftables.Rule{ - Table: m.workTable, - Chain: m.chainFwFilter, + Table: table, + Chain: chain, Exprs: expressions, }) - - if m.workTable6 != nil { - _ = m.rConn.AddRule(&nftables.Rule{ - Table: m.workTable6, - Chain: m.chainFwFilter6, - Exprs: expressions, - }) - } } -func (m *AclManager) addMarkAccept() { +func (m *AclManager) addMarkAccept(table *nftables.Table, chain *nftables.Chain) { // oifname "wt0" meta mark 0x000007e4 accept // iifname "wt0" meta mark 0x000007e4 accept ifaces := []expr.MetaKey{expr.MetaKeyIIFNAME, expr.MetaKeyOIFNAME} @@ -699,73 +792,44 @@ func (m *AclManager) addMarkAccept() { } _ = m.rConn.AddRule(&nftables.Rule{ - Table: m.workTable, - Chain: m.chainFwFilter, + Table: table, + Chain: chain, Exprs: expressions, }) - - if m.workTable6 != nil { - _ = m.rConn.AddRule(&nftables.Rule{ - Table: m.workTable6, - Chain: m.chainFwFilter6, - Exprs: expressions, - }) - } } } -func (m *AclManager) createChain(name string) (*nftables.Chain, *nftables.Chain) { +func (m *AclManager) createChain(name string, table *nftables.Table) *nftables.Chain { + chain := &nftables.Chain{ Name: name, - Table: m.workTable, + Table: table, } chain = m.rConn.AddChain(chain) - var chain6 *nftables.Chain = nil - if m.workTable6 != nil { - chain6 = &nftables.Chain{ - Name: name, - Table: m.workTable6, - } - chain6 = m.rConn.AddChain(chain6) - } - - return chain, chain6 + return chain } -func (m *AclManager) createFilterChainWithHook(name string, hookNum nftables.ChainHook) (*nftables.Chain, *nftables.Chain) { +func (m *AclManager) createFilterChainWithHook(name string, hookNum nftables.ChainHook, table *nftables.Table) *nftables.Chain { polAccept := nftables.ChainPolicyAccept chain := &nftables.Chain{ Name: name, - Table: m.workTable, + Table: table, Hooknum: hookNum, Priority: nftables.ChainPriorityFilter, Type: nftables.ChainTypeFilter, Policy: &polAccept, } - var chain6 *nftables.Chain = nil - if m.workTable6 != nil { - chain6 = &nftables.Chain{ - Name: name, - Table: m.workTable6, - Hooknum: hookNum, - Priority: nftables.ChainPriorityFilter, - Type: nftables.ChainTypeFilter, - Policy: &polAccept, - } - chain6 = m.rConn.AddChain(chain6) - } - - return m.rConn.AddChain(chain), chain6 + return m.rConn.AddChain(chain) } -func (m *AclManager) createPreroutingMangle() (*nftables.Chain, *nftables.Chain) { +func (m *AclManager) createPreroutingMangle(table *nftables.Table, forV6 bool) *nftables.Chain { polAccept := nftables.ChainPolicyAccept chain := &nftables.Chain{ Name: "netbird-acl-prerouting-filter", - Table: m.workTable, + Table: table, Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityMangle, Type: nftables.ChainTypeFilter, @@ -774,7 +838,24 @@ func (m *AclManager) createPreroutingMangle() (*nftables.Chain, *nftables.Chain) chain = m.rConn.AddChain(chain) - ip, _ := netip.AddrFromSlice(m.wgIface.Address().Network.IP.To4()) + rawIP := m.wgIface.Address().Network.IP.To4() + mask := m.wgIface.Address().Network.Mask + addrLen := uint32(4) + // source address position + srcAddrOffset := uint32(12) + dstAddrOffset := uint32(16) + nullArray := []byte{0x0, 0x0, 0x0, 0x0} + + if forV6 { + rawIP = m.wgIface.Address6().Network.IP.To16() + addrLen = 16 + mask = m.wgIface.Address6().Network.Mask + srcAddrOffset = uint32(8) + dstAddrOffset = uint32(24) + nullArray = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} + } + ip, _ := netip.AddrFromSlice(rawIP) + expressions := []expr.Any{ &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, &expr.Cmp{ @@ -785,15 +866,15 @@ func (m *AclManager) createPreroutingMangle() (*nftables.Chain, *nftables.Chain) &expr.Payload{ DestRegister: 2, Base: expr.PayloadBaseNetworkHeader, - Offset: 12, - Len: 4, + Offset: srcAddrOffset, + Len: addrLen, }, &expr.Bitwise{ SourceRegister: 2, DestRegister: 2, - Len: 4, - Xor: []byte{0x0, 0x0, 0x0, 0x0}, - Mask: m.wgIface.Address().Network.Mask, + Len: addrLen, + Xor: nullArray, + Mask: mask, }, &expr.Cmp{ Op: expr.CmpOpNeq, @@ -803,13 +884,13 @@ func (m *AclManager) createPreroutingMangle() (*nftables.Chain, *nftables.Chain) &expr.Payload{ DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, - Offset: 16, - Len: 4, + Offset: dstAddrOffset, + Len: addrLen, }, &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, - Data: m.wgIface.Address().IP.To4(), + Data: rawIP, }, &expr.Immediate{ Register: 1, @@ -822,81 +903,13 @@ func (m *AclManager) createPreroutingMangle() (*nftables.Chain, *nftables.Chain) }, } _ = m.rConn.AddRule(&nftables.Rule{ - Table: m.workTable, + Table: table, Chain: chain, Exprs: expressions, }) chain = m.rConn.AddChain(chain) - var chain6 *nftables.Chain = nil - if m.workTable6 != nil { - chain6 = &nftables.Chain{ - Name: "netbird-acl-prerouting-filter", - Table: m.workTable, - Hooknum: nftables.ChainHookPrerouting, - Priority: nftables.ChainPriorityMangle, - Type: nftables.ChainTypeFilter, - Policy: &polAccept, - } - - chain6 = m.rConn.AddChain(chain) - - ip, _ := netip.AddrFromSlice(m.wgIface.Address6().Network.IP.To16()) - expressions := []expr.Any{ - &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, - &expr.Cmp{ - Op: expr.CmpOpEq, - Register: 1, - Data: ifname(m.wgIface.Name()), - }, - &expr.Payload{ - DestRegister: 2, - Base: expr.PayloadBaseNetworkHeader, - Offset: 8, - Len: 16, - }, - &expr.Bitwise{ - SourceRegister: 2, - DestRegister: 2, - Len: 16, - Xor: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Mask: m.wgIface.Address6().Network.Mask, - }, - &expr.Cmp{ - Op: expr.CmpOpNeq, - Register: 2, - Data: ip.Unmap().AsSlice(), - }, - &expr.Payload{ - DestRegister: 1, - Base: expr.PayloadBaseNetworkHeader, - Offset: 24, - Len: 16, - }, - &expr.Cmp{ - Op: expr.CmpOpEq, - Register: 1, - Data: m.wgIface.Address6().IP.To16(), - }, - &expr.Immediate{ - Register: 1, - Data: postroutingMark, - }, - &expr.Meta{ - Key: expr.MetaKeyMARK, - SourceRegister: true, - Register: 1, - }, - } - _ = m.rConn.AddRule(&nftables.Rule{ - Table: m.workTable6, - Chain: chain, - Exprs: expressions, - }) - chain6 = m.rConn.AddChain(chain6) - } - - return chain, chain6 + return chain } func (m *AclManager) addDropExpressions(chain *nftables.Chain, ifaceKey expr.MetaKey) []expr.Any { @@ -917,7 +930,7 @@ func (m *AclManager) addDropExpressions(chain *nftables.Chain, ifaceKey expr.Met return nil } -func (m *AclManager) addJumpRuleToInputChain() { +func (m *AclManager) addJumpRuleToInputChain(table *nftables.Table, chain *nftables.Chain, inputChain *nftables.Chain) { expressions := []expr.Any{ &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, &expr.Cmp{ @@ -927,36 +940,15 @@ func (m *AclManager) addJumpRuleToInputChain() { }, &expr.Verdict{ Kind: expr.VerdictJump, - Chain: m.chainInputRules.Name, + Chain: inputChain.Name, }, } _ = m.rConn.AddRule(&nftables.Rule{ - Table: m.workTable, - Chain: m.chainFwFilter, + Table: table, + Chain: chain, Exprs: expressions, }) - - if m.workTable6 != nil { - expressions = []expr.Any{ - &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, - &expr.Cmp{ - Op: expr.CmpOpEq, - Register: 1, - Data: ifname(m.wgIface.Name()), - }, - &expr.Verdict{ - Kind: expr.VerdictJump, - Chain: m.chainInputRules6.Name, - }, - } - - _ = m.rConn.AddRule(&nftables.Rule{ - Table: m.workTable6, - Chain: m.chainFwFilter6, - Exprs: expressions, - }) - } } func (m *AclManager) addRouteAllowRule(chain *nftables.Chain, netIfName expr.MetaKey) { @@ -1187,7 +1179,6 @@ func (m *AclManager) addJumpRule(chain *nftables.Chain, to string, ifaceKey expr } func (m *AclManager) addIpToSet(ipsetName string, ip net.IP) (*nftables.Set, error) { - ipset, err := m.rConn.GetSetByName(m.workTable, ipsetName) rawIP := ip.To4() ipsetType := nftables.TypeIPAddr if rawIP == nil { @@ -1195,6 +1186,7 @@ func (m *AclManager) addIpToSet(ipsetName string, ip net.IP) (*nftables.Set, err ipsetType = nftables.TypeIP6Addr } if ipsetType == nftables.TypeIPAddr { + ipset, err := m.rConn.GetSetByName(m.workTable, ipsetName) if err != nil { if ipset, err = m.createSet(m.workTable, ipsetName, ipsetType); err != nil { return nil, fmt.Errorf("get set name: %v", err) @@ -1216,7 +1208,10 @@ func (m *AclManager) addIpToSet(ipsetName string, ip net.IP) (*nftables.Set, err if err := m.sConn.Flush(); err != nil { return nil, fmt.Errorf("flush add elements: %v", err) } + + return ipset, nil } else { + ipset, err := m.rConn.GetSetByName(m.workTable6, ipsetName) if err != nil { if ipset, err = m.createSet(m.workTable6, ipsetName, ipsetType); err != nil { return nil, fmt.Errorf("get set name: %v", err) @@ -1238,9 +1233,9 @@ func (m *AclManager) addIpToSet(ipsetName string, ip net.IP) (*nftables.Set, err if err := m.sConn.Flush(); err != nil { return nil, fmt.Errorf("flush add elements: %v", err) } - } - return ipset, nil + return ipset, nil + } } // createSet in given table by name @@ -1285,12 +1280,12 @@ func (m *AclManager) flushWithBackoff() (err error) { return } -func (m *AclManager) refreshRuleHandles(chain *nftables.Chain) error { - if m.workTable == nil || chain == nil { +func (m *AclManager) refreshRuleHandles(table *nftables.Table, chain *nftables.Chain) error { + if table == nil || chain == nil { return nil } - list, err := m.rConn.GetRules(m.workTable, chain) + list, err := m.rConn.GetRules(table, chain) if err != nil { return err } @@ -1317,6 +1312,10 @@ func generateRuleId( action firewall.Action, ipset *nftables.Set, ) string { + ipver := "v4" + if ip.To4() == nil { + ipver = "v6" + } rulesetID := ":" + strconv.Itoa(int(direction)) + ":" if sPort != nil { rulesetID += sPort.String() @@ -1330,7 +1329,7 @@ func generateRuleId( if ipset == nil { return "ip:" + ip.String() + rulesetID } - return "set:" + ipset.Name + rulesetID + return "set:" + ipver + ":" + ipset.Name + rulesetID } func generateRuleIdForMangle(ipset *nftables.Set, ip net.IP, proto firewall.Protocol, port *firewall.Port) string { // case of icmp port is empty diff --git a/client/firewall/nftables/manager_linux.go b/client/firewall/nftables/manager_linux.go index 4519a8a6908..7fcf9a52f02 100644 --- a/client/firewall/nftables/manager_linux.go +++ b/client/firewall/nftables/manager_linux.go @@ -54,6 +54,17 @@ func Create(context context.Context, wgIface iFaceMapper) (*Manager, error) { return m, nil } +// Resets the IPv6 Firewall Table to adapt to changes in IP addresses +func (m *Manager) ResetV6RulesAndAddr() error { + // TODO on disabling and re-enabling IPv6, not all rules are applied (most notably the routing forwarding rules). + err := m.aclManager.UpdateV6Address() + if err != nil { + return err + } + + return m.router.UpdateV6Address() +} + // AddFiltering rule to the firewall // // If comment argument is empty firewall manager should set diff --git a/client/firewall/nftables/route_linux.go b/client/firewall/nftables/route_linux.go index a1a7e0aef24..f0678d3dfc1 100644 --- a/client/firewall/nftables/route_linux.go +++ b/client/firewall/nftables/route_linux.go @@ -24,8 +24,6 @@ const ( userDataAcceptForwardRuleDst = "frwacceptdst" ) -// TODO ipv6 everywhere here. - // some presets for building nftable rules var ( zeroXor = binaryutil.NativeEndian.PutUint32(0) @@ -53,6 +51,7 @@ type router struct { chains6 map[string]*nftables.Chain // rules is useful to avoid duplicates and to get missing attributes that we don't have when adding new rules rules map[string]*nftables.Rule + rules6 map[string]*nftables.Rule isDefaultFwdRulesEnabled bool } @@ -68,10 +67,11 @@ func newRouter(parentCtx context.Context, workTable *nftables.Table, workTable6 chains: make(map[string]*nftables.Chain), chains6: make(map[string]*nftables.Chain), rules: make(map[string]*nftables.Rule), + rules6: make(map[string]*nftables.Rule), } var err error - r.filterTable, r.filterTable6, err = r.loadFilterTables(workTable6 != nil) + r.filterTable, r.filterTable6, err = r.loadFilterTables() if err != nil { if errors.Is(err, errFilterTableNotFound) { log.Warnf("table 'filter' not found for forward rules for one of the supported address families-") @@ -89,6 +89,12 @@ func newRouter(parentCtx context.Context, workTable *nftables.Table, workTable6 if err != nil { log.Errorf("failed to create containers for route: %s", err) } + + err = r.createContainers6() + if err != nil { + log.Errorf("failed to create v6 containers for route: %s", err) + } + return r, err } @@ -104,7 +110,26 @@ func (r *router) ResetForwardRules() { } } -func (r *router) loadFilterTables(include6 bool) (*nftables.Table, *nftables.Table, error) { +func (r *router) UpdateV6Address() error { + err := r.createContainers6() + if err != nil { + return err + } + + for name, rule := range r.rules6 { + rule = &nftables.Rule{ + Table: r.workTable6, + Chain: r.chains6[rule.Chain.Name], + Exprs: rule.Exprs, + UserData: rule.UserData, + } + r.rules6[name] = r.conn.AddRule(rule) + } + + return r.conn.Flush() +} + +func (r *router) loadFilterTables() (*nftables.Table, *nftables.Table, error) { tables, err := r.conn.ListTablesOfFamily(nftables.TableFamilyIPv4) if err != nil { return nil, nil, fmt.Errorf("nftables: unable to list tables: %v", err) @@ -119,16 +144,14 @@ func (r *router) loadFilterTables(include6 bool) (*nftables.Table, *nftables.Tab } var table6 *nftables.Table = nil - if include6 { - tables, err = r.conn.ListTablesOfFamily(nftables.TableFamilyIPv6) - if err != nil { - return nil, nil, fmt.Errorf("nftables: unable to list tables: %v", err) - } - for _, table := range tables { - if table.Name == "filter" { - table6 = table - break - } + tables, err = r.conn.ListTablesOfFamily(nftables.TableFamilyIPv6) + if err != nil { + return nil, nil, fmt.Errorf("nftables: unable to list tables: %v", err) + } + for _, table := range tables { + if table.Name == "filter" { + table6 = table + break } } @@ -155,22 +178,33 @@ func (r *router) createContainers() error { Type: nftables.ChainTypeNAT, }) - if r.workTable6 != nil { - r.chains6[chainNameRouteingFw] = r.conn.AddChain(&nftables.Chain{ - Name: chainNameRouteingFw, - Table: r.workTable6, - }) + err := r.refreshRulesMap() + if err != nil { + log.Errorf("failed to clean up rules from FORWARD chain: %s", err) + } - r.chains6[chainNameRoutingNat] = r.conn.AddChain(&nftables.Chain{ - Name: chainNameRoutingNat, - Table: r.workTable6, - Hooknum: nftables.ChainHookPostrouting, - Priority: nftables.ChainPriorityNATSource - 1, - Type: nftables.ChainTypeNAT, - }) + err = r.conn.Flush() + if err != nil { + return fmt.Errorf("nftables: unable to initialize table: %v", err) } + return nil +} +func (r *router) createContainers6() error { - err := r.refreshRulesMap() + r.chains6[chainNameRouteingFw] = r.conn.AddChain(&nftables.Chain{ + Name: chainNameRouteingFw, + Table: r.workTable6, + }) + + r.chains6[chainNameRoutingNat] = r.conn.AddChain(&nftables.Chain{ + Name: chainNameRoutingNat, + Table: r.workTable6, + Hooknum: nftables.ChainHookPostrouting, + Priority: nftables.ChainPriorityNATSource - 1, + Type: nftables.ChainTypeNAT, + }) + + err := r.refreshRulesMap6() if err != nil { log.Errorf("failed to clean up rules from FORWARD chain: %s", err) } @@ -248,13 +282,13 @@ func (r *router) insertRoutingRule(format, chainName string, pair manager.Router } } - table, chain := r.workTable, r.chains[chainName] + table, chain, rules := r.workTable, r.chains[chainName], r.rules parsedIp, _, _ := net.ParseCIDR(pair.Source) if parsedIp.To4() == nil { - table, chain = r.workTable6, r.chains6[chainName] + table, chain, rules = r.workTable6, r.chains6[chainName], r.rules6 } - r.rules[ruleKey] = r.conn.InsertRule(&nftables.Rule{ + rules[ruleKey] = r.conn.InsertRule(&nftables.Rule{ Table: table, Chain: chain, Exprs: expression, @@ -279,7 +313,7 @@ func (r *router) acceptForwardRule(sourceNetwork string) { })...) rule := &nftables.Rule{ - Table: r.filterTable, + Table: table, Chain: &nftables.Chain{ Name: "FORWARD", Table: table, @@ -304,7 +338,7 @@ func (r *router) acceptForwardRule(sourceNetwork string) { })...) rule = &nftables.Rule{ - Table: r.filterTable, + Table: table, Chain: &nftables.Chain{ Name: "FORWARD", Table: table, @@ -401,6 +435,23 @@ func (r *router) refreshRulesMap() error { return nil } +// refreshRulesMap6 refreshes the rule map for IPv6 with the latest rules. this is useful to avoid +// duplicates and to get missing attributes that we don't have when adding new rules +func (r *router) refreshRulesMap6() error { + for _, chain := range r.chains6 { + rules, err := r.conn.GetRules(chain.Table, chain) + if err != nil { + return fmt.Errorf("nftables: unable to list rules: %v", err) + } + for _, rule := range rules { + if len(rule.UserData) > 0 { + r.rules6[string(rule.UserData)] = rule + } + } + } + return nil +} + func (r *router) cleanUpDefaultForwardRules() error { if r.filterTable == nil { r.isDefaultFwdRulesEnabled = false diff --git a/client/firewall/uspfilter/uspfilter.go b/client/firewall/uspfilter/uspfilter.go index 97c66446f50..5baf8be1173 100644 --- a/client/firewall/uspfilter/uspfilter.go +++ b/client/firewall/uspfilter/uspfilter.go @@ -70,6 +70,10 @@ func CreateWithNativeFirewall(iface IFaceMapper, nativeFirewall firewall.Manager return mgr, nil } +func (m *Manager) ResetV6RulesAndAddr() error { + return nil +} + func create(iface IFaceMapper) (*Manager, error) { m := &Manager{ decoders: sync.Pool{ diff --git a/client/internal/acl/manager.go b/client/internal/acl/manager.go index 6d9a9945205..05ad6b643f7 100644 --- a/client/internal/acl/manager.go +++ b/client/internal/acl/manager.go @@ -19,6 +19,7 @@ import ( // Manager is a ACL rules manager type Manager interface { ApplyFiltering(networkMap *mgmProto.NetworkMap) + ResetV6RulesAndAddr() error } // DefaultManager uses firewall manager to handle @@ -26,16 +27,37 @@ type DefaultManager struct { firewall firewall.Manager ipsetCounter int rulesPairs map[string][]firewall.Rule + rulesPairs6 map[string][]firewall.Rule + v6Active bool mutex sync.Mutex } func NewDefaultManager(fm firewall.Manager) *DefaultManager { return &DefaultManager{ - firewall: fm, - rulesPairs: make(map[string][]firewall.Rule), + firewall: fm, + rulesPairs: make(map[string][]firewall.Rule), + rulesPairs6: make(map[string][]firewall.Rule), } } +func (d *DefaultManager) ResetV6RulesAndAddr() error { + for _, rules := range d.rulesPairs6 { + for _, r := range rules { + err := d.firewall.DeleteRule(r) + if err != nil { + return err + } + } + } + err := d.firewall.ResetV6RulesAndAddr() + if err != nil { + return err + } + d.rulesPairs6 = make(map[string][]firewall.Rule) + + return nil +} + // ApplyFiltering firewall rules to the local firewall manager processed by ACL policy. // // If allowByDefault is true it appends allow ALL traffic rules to input and output chains. @@ -114,6 +136,7 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) { } newRulePairs := make(map[string][]firewall.Rule) + newRulePairs6 := make(map[string][]firewall.Rule) ipsetByRuleSelectors := make(map[string]string) for _, r := range rules { @@ -126,7 +149,7 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) { ipsetName = fmt.Sprintf("nb%07d", d.ipsetCounter) ipsetByRuleSelectors[selector] = ipsetName } - pairID, rulePair, err := d.protoRuleToFirewallRule(r, ipsetName) + pairID, rulePair, rulePair6, err := d.protoRuleToFirewallRule(r, ipsetName) if err != nil { log.Errorf("failed to apply firewall rule: %+v, %v", r, err) d.rollBack(newRulePairs) @@ -135,6 +158,8 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) { if len(rules) > 0 { d.rulesPairs[pairID] = rulePair newRulePairs[pairID] = rulePair + d.rulesPairs6[pairID] = rulePair6 + newRulePairs6[pairID] = rulePair6 } } @@ -149,16 +174,29 @@ func (d *DefaultManager) ApplyFiltering(networkMap *mgmProto.NetworkMap) { delete(d.rulesPairs, pairID) } } + for pairID, rules := range d.rulesPairs6 { + if _, ok := newRulePairs6[pairID]; !ok { + for _, rule := range rules { + if err := d.firewall.DeleteRule(rule); err != nil { + log.Errorf("failed to delete firewall rule: %v", err) + continue + } + } + delete(d.rulesPairs6, pairID) + } + } + d.rulesPairs = newRulePairs + d.rulesPairs6 = newRulePairs6 } func (d *DefaultManager) protoRuleToFirewallRule( r *mgmProto.FirewallRule, ipsetName string, -) (string, []firewall.Rule, error) { +) (string, []firewall.Rule, []firewall.Rule, error) { ip := net.ParseIP(r.PeerIP) if ip == nil { - return "", nil, fmt.Errorf("invalid IP address, skipping firewall rule") + return "", nil, nil, fmt.Errorf("invalid IP address, skipping firewall rule") } var ip6 *net.IP = nil @@ -166,56 +204,78 @@ func (d *DefaultManager) protoRuleToFirewallRule( ip6tmp := net.ParseIP(r.PeerIP6) ip6 = &ip6tmp if ip6 == nil { - return "", nil, fmt.Errorf("invalid IP address, skipping firewall rule") + return "", nil, nil, fmt.Errorf("invalid IP address, skipping firewall rule") } } protocol, err := convertToFirewallProtocol(r.Protocol) if err != nil { - return "", nil, fmt.Errorf("skipping firewall rule: %s", err) + return "", nil, nil, fmt.Errorf("skipping firewall rule: %s", err) } action, err := convertFirewallAction(r.Action) if err != nil { - return "", nil, fmt.Errorf("skipping firewall rule: %s", err) + return "", nil, nil, fmt.Errorf("skipping firewall rule: %s", err) } var port *firewall.Port if r.Port != "" { value, err := strconv.Atoi(r.Port) if err != nil { - return "", nil, fmt.Errorf("invalid port, skipping firewall rule") + return "", nil, nil, fmt.Errorf("invalid port, skipping firewall rule") } port = &firewall.Port{ Values: []int{value}, } } + var rules []firewall.Rule + var rules6 []firewall.Rule + ruleID := d.getRuleID(ip, ip6, protocol, int(r.Direction), port, action, "") if rulesPair, ok := d.rulesPairs[ruleID]; ok { - return ruleID, rulesPair, nil + rules = rulesPair + } + if rulesPair6, ok := d.rulesPairs6[ruleID]; ok && ip6 != nil { + rules6 = rulesPair6 } - var rules []firewall.Rule - switch r.Direction { - case mgmProto.FirewallRule_IN: - rules, err = d.addInRules(ip, ip6, protocol, port, action, ipsetName, "") - case mgmProto.FirewallRule_OUT: - rules, err = d.addOutRules(ip, ip6, protocol, port, action, ipsetName, "") - default: - return "", nil, fmt.Errorf("invalid direction, skipping firewall rule") + if rules == nil { + switch r.Direction { + case mgmProto.FirewallRule_IN: + rules, err = d.addInRules(ip, protocol, port, action, ipsetName, "") + case mgmProto.FirewallRule_OUT: + rules, err = d.addOutRules(ip, protocol, port, action, ipsetName, "") + default: + return "", nil, nil, fmt.Errorf("invalid direction, skipping firewall rule") + } } if err != nil { - return "", nil, err + return "", nil, nil, err } - return ruleID, rules, nil + if ip6 != nil && rules6 == nil { + switch r.Direction { + case mgmProto.FirewallRule_IN: + rules6, err = d.addInRules(*ip6, protocol, port, action, ipsetName, "") + case mgmProto.FirewallRule_OUT: + rules6, err = d.addOutRules(*ip6, protocol, port, action, ipsetName, "") + default: + return "", nil, nil, fmt.Errorf("invalid direction, skipping firewall rule") + } + + } + + if err != nil && err.Error() != "failed to add firewall rule: attempted to configure filtering for IPv6 address even though IPv6 is not active" { + return "", rules, nil, err + } + + return ruleID, rules, rules6, nil } func (d *DefaultManager) addInRules( ip net.IP, - ip6 *net.IP, protocol firewall.Protocol, port *firewall.Port, action firewall.Action, @@ -224,47 +284,28 @@ func (d *DefaultManager) addInRules( ) ([]firewall.Rule, error) { var rules []firewall.Rule rule, err := d.firewall.AddFiltering( - ip, protocol, nil, port, firewall.RuleDirectionIN, action, ipsetName+"-v4", comment) + ip, protocol, nil, port, firewall.RuleDirectionIN, action, ipsetName, comment) if err != nil { return nil, fmt.Errorf("failed to add firewall rule: %v", err) } rules = append(rules, rule...) - if ip6 != nil { - rule, err := d.firewall.AddFiltering( - *ip6, protocol, nil, port, firewall.RuleDirectionIN, action, ipsetName+"-v6", comment) - if err != nil { - return nil, fmt.Errorf("failed to add firewall rule: %v", err) - } - rules = append(rules, rule...) - } - if shouldSkipInvertedRule(protocol, port) { return rules, nil } rule, err = d.firewall.AddFiltering( - ip, protocol, port, nil, firewall.RuleDirectionOUT, action, ipsetName+"-v4", comment) + ip, protocol, port, nil, firewall.RuleDirectionOUT, action, ipsetName, comment) if err != nil { return nil, fmt.Errorf("failed to add firewall rule: %v", err) } rules = append(rules, rule...) - if ip6 != nil { - rule, err = d.firewall.AddFiltering( - *ip6, protocol, port, nil, firewall.RuleDirectionOUT, action, ipsetName+"-v6", comment) - if err != nil { - return nil, fmt.Errorf("failed to add firewall rule: %v", err) - } - rules = append(rules, rule...) - } - return rules, nil } func (d *DefaultManager) addOutRules( ip net.IP, - ip6 *net.IP, protocol firewall.Protocol, port *firewall.Port, action firewall.Action, @@ -279,15 +320,6 @@ func (d *DefaultManager) addOutRules( } rules = append(rules, rule...) - if ip6 != nil { - rule, err = d.firewall.AddFiltering( - *ip6, protocol, nil, port, firewall.RuleDirectionOUT, action, ipsetName+"-v6", comment) - if err != nil { - return nil, fmt.Errorf("failed to add firewall rule: %v", err) - } - rules = append(rules, rule...) - } - if shouldSkipInvertedRule(protocol, port) { return rules, nil } @@ -299,15 +331,6 @@ func (d *DefaultManager) addOutRules( } rules = append(rules, rule...) - if ip6 != nil { - rule, err = d.firewall.AddFiltering( - *ip6, protocol, port, nil, firewall.RuleDirectionIN, action, ipsetName+"-v6", comment) - rules = append(rules, rule...) - if err != nil { - return nil, fmt.Errorf("failed to add firewall rule: %v", err) - } - } - return rules, nil } diff --git a/client/internal/engine.go b/client/internal/engine.go index df1f27e6de9..5e8315b55fe 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -554,6 +554,11 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error { return err } e.config.WgAddr6 = conf.Address6 + + err = e.acl.ResetV6RulesAndAddr() + if err != nil { + return err + } log.Infof("updated peer IPv6 address from %s to %s", oldAddr, conf.Address6) } diff --git a/client/internal/routemanager/client.go b/client/internal/routemanager/client.go index 8a8e8c83216..b42e4400e0c 100644 --- a/client/internal/routemanager/client.go +++ b/client/internal/routemanager/client.go @@ -3,6 +3,7 @@ package routemanager import ( "context" "fmt" + "net" "net/netip" log "github.com/sirupsen/logrus" @@ -35,6 +36,7 @@ type clientNetwork struct { peerStateUpdate chan struct{} routePeersNotifiers map[string]chan struct{} chosenRoute *route.Route + chosenIP *net.IP network netip.Prefix updateSerial uint64 } @@ -178,11 +180,7 @@ func (c *clientNetwork) removeRouteFromPeerAndSystem() error { if err != nil { return err } - addr := c.wgInterface.Address().IP.String() - if c.chosenRoute.Network.Addr().Is6() { - addr = c.wgInterface.Address6().IP.String() - } - err = removeFromRouteTableIfNonSystem(c.network, addr) + err = removeFromRouteTableIfNonSystem(c.network, c.chosenIP.String()) if err != nil { return fmt.Errorf("couldn't remove route %s from system, err: %v", c.network, err) @@ -221,16 +219,17 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem() error { return err } } else { - gwAddr := c.wgInterface.Address().IP.String() + gwAddr := c.wgInterface.Address().IP + c.chosenIP = &gwAddr if c.network.Addr().Is6() { if c.wgInterface.Address6() == nil { return fmt.Errorf("Could not assign IPv6 route %s for peer %s because no IPv6 address is assigned", c.network.String(), c.wgInterface.Address().IP.String()) } - gwAddr = c.wgInterface.Address6().IP.String() + c.chosenIP = &c.wgInterface.Address6().IP } - err = addToRouteTableIfNoExists(c.network, gwAddr, c.wgInterface.Name()) + err = addToRouteTableIfNoExists(c.network, c.chosenIP.String(), c.wgInterface.Name()) if err != nil { return fmt.Errorf("route %s couldn't be added for peer %s, err: %v", c.network.String(), c.wgInterface.Address().IP.String(), err) diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index 5d3bc46722f..f3a9f10de02 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -250,14 +250,14 @@ func mapError(err error) error { func extractPeerMeta(loginReq *proto.LoginRequest) nbpeer.PeerSystemMeta { return nbpeer.PeerSystemMeta{ - Hostname: loginReq.GetMeta().GetHostname(), - GoOS: loginReq.GetMeta().GetGoOS(), - Kernel: loginReq.GetMeta().GetKernel(), - Core: loginReq.GetMeta().GetCore(), - Platform: loginReq.GetMeta().GetPlatform(), - OS: loginReq.GetMeta().GetOS(), - WtVersion: loginReq.GetMeta().GetWiretrusteeVersion(), - UIVersion: loginReq.GetMeta().GetUiVersion(), + Hostname: loginReq.GetMeta().GetHostname(), + GoOS: loginReq.GetMeta().GetGoOS(), + Kernel: loginReq.GetMeta().GetKernel(), + Core: loginReq.GetMeta().GetCore(), + Platform: loginReq.GetMeta().GetPlatform(), + OS: loginReq.GetMeta().GetOS(), + WtVersion: loginReq.GetMeta().GetWiretrusteeVersion(), + UIVersion: loginReq.GetMeta().GetUiVersion(), Ipv6Supported: loginReq.GetMeta().GetIpv6Supported(), } } @@ -435,12 +435,12 @@ func toPeerConfig(peer *nbpeer.Peer, network *Network, dnsName string) *proto.Pe } } -func toRemotePeerConfig(peers []*nbpeer.Peer, dnsName string) []*proto.RemotePeerConfig { +func toRemotePeerConfig(peers []*nbpeer.Peer, dnsName string, v6Enabled bool) []*proto.RemotePeerConfig { remotePeers := []*proto.RemotePeerConfig{} for _, rPeer := range peers { fqdn := rPeer.FQDN(dnsName) allowedIps := []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)} - if rPeer.IP6 != nil { + if rPeer.IP6 != nil && v6Enabled { allowedIps = append(allowedIps, fmt.Sprintf(AllowedIP6sFormat, *rPeer.IP6)) } remotePeers = append(remotePeers, &proto.RemotePeerConfig{ @@ -458,13 +458,13 @@ func toSyncResponse(config *Config, peer *nbpeer.Peer, turnCredentials *TURNCred pConfig := toPeerConfig(peer, networkMap.Network, dnsName) - remotePeers := toRemotePeerConfig(networkMap.Peers, dnsName) + remotePeers := toRemotePeerConfig(networkMap.Peers, dnsName, peer.IP6 != nil) routesUpdate := toProtocolRoutes(networkMap.Routes) dnsUpdate := toProtocolDNSConfig(networkMap.DNSConfig) - offlinePeers := toRemotePeerConfig(networkMap.OfflinePeers, dnsName) + offlinePeers := toRemotePeerConfig(networkMap.OfflinePeers, dnsName, peer.IP6 != nil) firewallRules := toProtocolFirewallRules(networkMap.FirewallRules) From 1fa25deb6fe812f2930c0cbd621ac49437587d6f Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Fri, 12 Jan 2024 03:56:14 +0100 Subject: [PATCH 09/42] Fix build issues for non-linux systems --- client/internal/routemanager/systemops_android.go | 2 +- client/internal/routemanager/systemops_ios.go | 2 +- client/system/info_linux.go | 3 ++- iface/iface_android.go | 7 ++++++- iface/iface_darwin.go | 2 ++ iface/iface_windows.go | 2 ++ iface/tun.go | 2 +- iface/tun_android.go | 9 ++++++--- iface/tun_ios.go | 1 + 9 files changed, 22 insertions(+), 8 deletions(-) diff --git a/client/internal/routemanager/systemops_android.go b/client/internal/routemanager/systemops_android.go index 950a268434c..08675a16aa6 100644 --- a/client/internal/routemanager/systemops_android.go +++ b/client/internal/routemanager/systemops_android.go @@ -4,7 +4,7 @@ import ( "net/netip" ) -func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error { +func addToRouteTableIfNoExists(prefix netip.Prefix, addr string, devName string) error { return nil } diff --git a/client/internal/routemanager/systemops_ios.go b/client/internal/routemanager/systemops_ios.go index aae0f8dc8f2..adfdc7e0816 100644 --- a/client/internal/routemanager/systemops_ios.go +++ b/client/internal/routemanager/systemops_ios.go @@ -6,7 +6,7 @@ import ( "net/netip" ) -func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error { +func addToRouteTableIfNoExists(prefix netip.Prefix, addr string, devName string) error { return nil } diff --git a/client/system/info_linux.go b/client/system/info_linux.go index 5b9f2a519d6..3a435ff6e96 100644 --- a/client/system/info_linux.go +++ b/client/system/info_linux.go @@ -8,6 +8,7 @@ import ( "context" "github.com/netbirdio/netbird/client/firewall" "github.com/netbirdio/netbird/iface" + "github.com/netbirdio/netbird/iface/netstack" "os" "os/exec" "runtime" @@ -91,5 +92,5 @@ func _getReleaseInfo() string { func _checkIPv6Support() bool { return firewall.SupportsIPv6() && - iface.WireGuardModuleIsLoaded() + iface.WireGuardModuleIsLoaded() && !netstack.IsEnabled() } diff --git a/iface/iface_android.go b/iface/iface_android.go index d1876e4955d..ff39ad65ca4 100644 --- a/iface/iface_android.go +++ b/iface/iface_android.go @@ -3,11 +3,16 @@ package iface import ( "fmt" + log "github.com/sirupsen/logrus" + "github.com/pion/transport/v3" ) // NewWGIFace Creates a new WireGuard interface instance -func NewWGIFace(iFaceName string, address string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *MobileIFaceArguments) (*WGIface, error) { +func NewWGIFace(iFaceName string, address string, address6 string, wgPort int, wgPrivKey string, mtu int, transportNet transport.Net, args *MobileIFaceArguments) (*WGIface, error) { + if address6 != "" { + log.Errorf("Attempted to configure IPv6 address %s on unsupported operating system", address6) + } wgAddress, err := parseWGAddress(address) if err != nil { return nil, err diff --git a/iface/iface_darwin.go b/iface/iface_darwin.go index 1eb4d023962..0fc2c6deabe 100644 --- a/iface/iface_darwin.go +++ b/iface/iface_darwin.go @@ -6,6 +6,8 @@ package iface import ( "fmt" + log "github.com/sirupsen/logrus" + "github.com/pion/transport/v3" "github.com/netbirdio/netbird/iface/netstack" diff --git a/iface/iface_windows.go b/iface/iface_windows.go index 60857673cd0..dc58ace35a1 100644 --- a/iface/iface_windows.go +++ b/iface/iface_windows.go @@ -3,6 +3,8 @@ package iface import ( "fmt" + log "github.com/sirupsen/logrus" + "github.com/pion/transport/v3" "github.com/netbirdio/netbird/iface/netstack" diff --git a/iface/tun.go b/iface/tun.go index 8867548d5f0..4f3e1b69d2e 100644 --- a/iface/tun.go +++ b/iface/tun.go @@ -12,7 +12,7 @@ type wgTunDevice interface { Up() (*bind.UniversalUDPMuxDefault, error) UpdateAddr(address WGAddress) error WgAddress() WGAddress - UpdateAddr6(address6 *WGAddress) error + UpdateAddr6(addr6 *WGAddress) error WgAddress6() *WGAddress DeviceName() string Close() error diff --git a/iface/tun_android.go b/iface/tun_android.go index d6b1988a169..08f531c3870 100644 --- a/iface/tun_android.go +++ b/iface/tun_android.go @@ -4,6 +4,7 @@ package iface import ( + "fmt" "strings" "github.com/pion/transport/v3" @@ -98,9 +99,11 @@ func (t *wgTunDevice) UpdateAddr(addr WGAddress) error { return nil } -func (t *wgTunDevice) UpdateAddr6(addr WGAddress) error { - // todo implement - return nil +func (t *wgTunDevice) UpdateAddr6(addr6 *WGAddress) error { + if addr6 == nil { + return nil + } + return fmt.Errorf("IPv6 is not supported on this operating system") } func (t *wgTunDevice) Close() error { diff --git a/iface/tun_ios.go b/iface/tun_ios.go index 5b5ee886be2..1c90ccb80e9 100644 --- a/iface/tun_ios.go +++ b/iface/tun_ios.go @@ -4,6 +4,7 @@ package iface import ( + "fmt" "os" "github.com/pion/transport/v3" From 32b2b2f3f66618a8356df413d4499b4e166c0bc2 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Fri, 12 Jan 2024 04:17:29 +0100 Subject: [PATCH 10/42] Fix intermittent disconnections when IPv6 is enabled --- client/internal/peer/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/internal/peer/conn.go b/client/internal/peer/conn.go index b4c969dfcb4..ad7b0a2ba50 100644 --- a/client/internal/peer/conn.go +++ b/client/internal/peer/conn.go @@ -424,7 +424,7 @@ func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int, rem log.Warnf("unable to save peer's state, got error: %v", err) } - _, ipNet, err := net.ParseCIDR(conn.config.WgConfig.AllowedIps) + _, ipNet, err := net.ParseCIDR(strings.Split(conn.config.WgConfig.AllowedIps, ",")[0]) if err != nil { return nil, err } From 8e6f530b009bdf0b61b2222634655471c338cbdd Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Sat, 13 Jan 2024 00:21:24 +0100 Subject: [PATCH 11/42] Fix test issues and make some minor revisions --- client/firewall/nftables/acl_linux.go | 25 ++++++++++--------- client/firewall/nftables/manager_linux.go | 1 - client/firewall/nftables/router_linux_test.go | 24 +++++++++++------- client/firewall/uspfilter/uspfilter_test.go | 4 +++ client/internal/acl/manager.go | 2 +- client/internal/acl/mocks/iface_mapper.go | 4 +-- client/internal/engine_test.go | 8 +++--- client/internal/routemanager/manager_test.go | 2 +- .../routemanager/systemops_nonandroid_test.go | 10 ++++---- iface/iface_test.go | 16 ++++++------ .../server/http/accounts_handler_test.go | 2 +- 11 files changed, 55 insertions(+), 43 deletions(-) diff --git a/client/firewall/nftables/acl_linux.go b/client/firewall/nftables/acl_linux.go index a74e7540db0..f9f022d96fe 100644 --- a/client/firewall/nftables/acl_linux.go +++ b/client/firewall/nftables/acl_linux.go @@ -30,13 +30,14 @@ const ( chainNameOutputFilter = "netbird-acl-output-filter" chainNameForwardFilter = "netbird-acl-forward-filter" - setNameHostIpAddrs = "netbird-acl-host-ips" - allowNetbirdInputRuleID = "allow Netbird incoming traffic" ) var ( - anyIP = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + anyIP = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + + nullAddress4 = []byte{0x0, 0x0, 0x0, 0x0} + nullAddress6 = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} postroutingMark = []byte{0xe4, 0x7, 0x0, 0x00} ) @@ -256,7 +257,7 @@ func (m *AclManager) DeleteRule(rule firewall.Rule) error { return nil } -// createDefaultAllowRules In case if the USP firewall manager can use the native firewall manager we must to create allow rules for +// createDefaultAllowRules In case if the USP firewall manager can use the native firewall manager we must create allow rules for // input and output chains func (m *AclManager) createDefaultAllowRules() error { expIn := []expr.Any{ @@ -920,7 +921,7 @@ func (m *AclManager) createPreroutingMangle(table *nftables.Table, forV6 bool) * // source address position srcAddrOffset := uint32(12) dstAddrOffset := uint32(16) - nullArray := []byte{0x0, 0x0, 0x0, 0x0} + nullArray := nullAddress4 if forV6 { rawIP = m.wgIface.Address6().Network.IP.To16() @@ -928,7 +929,7 @@ func (m *AclManager) createPreroutingMangle(table *nftables.Table, forV6 bool) * mask = m.wgIface.Address6().Network.Mask srcAddrOffset = uint32(8) dstAddrOffset = uint32(24) - nullArray = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} + nullArray = nullAddress6 } ip, _ := netip.AddrFromSlice(rawIP) @@ -1031,14 +1032,14 @@ func (m *AclManager) addRouteAllowRule(chain *nftables.Chain, netIfName expr.Met srcAddrOffset := uint32(12) dstAddrOffset := uint32(16) mask := m.wgIface.Address().Network.Mask - nullArray := []byte{0x0, 0x0, 0x0, 0x0} + nullArray := nullAddress4 if chain.Table.Family == nftables.TableFamilyIPv6 { ip, _ = netip.AddrFromSlice(m.wgIface.Address6().Network.IP.To16()) addrLen = 16 srcAddrOffset = 8 dstAddrOffset = 24 mask = m.wgIface.Address6().Network.Mask - nullArray = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} + nullArray = nullAddress6 } var srcOp, dstOp expr.CmpOp @@ -1109,14 +1110,14 @@ func (m *AclManager) addFwdAllow(chain *nftables.Chain, iifname expr.MetaKey) { srcAddrOffset := uint32(12) dstAddrOffset := uint32(16) mask := m.wgIface.Address().Network.Mask - nullArray := []byte{0x0, 0x0, 0x0, 0x0} + nullArray := nullAddress4 if chain.Table.Family == nftables.TableFamilyIPv6 { ip, _ = netip.AddrFromSlice(m.wgIface.Address6().Network.IP.To16()) addrLen = 16 srcAddrOffset = 8 dstAddrOffset = 24 mask = m.wgIface.Address6().Network.Mask - nullArray = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} + nullArray = nullAddress6 } var srcOp, dstOp expr.CmpOp @@ -1187,14 +1188,14 @@ func (m *AclManager) addJumpRule(chain *nftables.Chain, to string, ifaceKey expr srcAddrOffset := uint32(12) dstAddrOffset := uint32(16) mask := m.wgIface.Address().Network.Mask - nullArray := []byte{0x0, 0x0, 0x0, 0x0} + nullArray := nullAddress4 if chain.Table.Family == nftables.TableFamilyIPv6 { ip, _ = netip.AddrFromSlice(m.wgIface.Address6().Network.IP.To16()) addrLen = 16 srcAddrOffset = 8 dstAddrOffset = 24 mask = m.wgIface.Address6().Network.Mask - nullArray = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} + nullArray = nullAddress6 } expressions := []expr.Any{ diff --git a/client/firewall/nftables/manager_linux.go b/client/firewall/nftables/manager_linux.go index 4f13868acda..af495aba0ad 100644 --- a/client/firewall/nftables/manager_linux.go +++ b/client/firewall/nftables/manager_linux.go @@ -56,7 +56,6 @@ func Create(context context.Context, wgIface iFaceMapper) (*Manager, error) { // Resets the IPv6 Firewall Table to adapt to changes in IP addresses func (m *Manager) ResetV6RulesAndAddr() error { - // TODO on disabling and re-enabling IPv6, not all rules are applied (most notably the routing forwarding rules). err := m.aclManager.UpdateV6Address() if err != nil { return err diff --git a/client/firewall/nftables/router_linux_test.go b/client/firewall/nftables/router_linux_test.go index aa1224a5a84..253c0356af3 100644 --- a/client/firewall/nftables/router_linux_test.go +++ b/client/firewall/nftables/router_linux_test.go @@ -29,7 +29,7 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) { t.Skip("nftables not supported on this OS") } - table, err := createWorkTable() + table, table6, err := createWorkTables() if err != nil { t.Fatal(err) } @@ -38,7 +38,7 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) { for _, testCase := range test.InsertRuleTestCases { t.Run(testCase.Name, func(t *testing.T) { - manager, err := newRouter(context.TODO(), table) + manager, err := newRouter(context.TODO(), table, table6) require.NoError(t, err, "failed to create router") nftablesTestingClient := &nftables.Conn{} @@ -131,7 +131,7 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) { t.Skip("nftables not supported on this OS") } - table, err := createWorkTable() + table, table6, err := createWorkTables() if err != nil { t.Fatal(err) } @@ -140,7 +140,7 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) { for _, testCase := range test.RemoveRuleTestCases { t.Run(testCase.Name, func(t *testing.T) { - manager, err := newRouter(context.TODO(), table) + manager, err := newRouter(context.TODO(), table, table6) require.NoError(t, err, "failed to create router") nftablesTestingClient := &nftables.Conn{} @@ -238,27 +238,33 @@ func isIptablesClientAvailable(client *iptables.IPTables) bool { return err == nil } -func createWorkTable() (*nftables.Table, error) { +func createWorkTables() (*nftables.Table, *nftables.Table, error) { sConn, err := nftables.New(nftables.AsLasting()) if err != nil { - return nil, err + return nil, nil, err } tables, err := sConn.ListTablesOfFamily(nftables.TableFamilyIPv4) if err != nil { - return nil, err + return nil, nil, err } - for _, t := range tables { + tables6, err := sConn.ListTablesOfFamily(nftables.TableFamilyIPv6) + if err != nil { + return nil, nil, err + } + + for _, t := range append(tables, tables6...) { if t.Name == tableName { sConn.DelTable(t) } } table := sConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv4}) + table6 := sConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv6}) err = sConn.Flush() - return table, err + return table, table6, err } func deleteWorkTable() { diff --git a/client/firewall/uspfilter/uspfilter_test.go b/client/firewall/uspfilter/uspfilter_test.go index 514a9053935..031513304bb 100644 --- a/client/firewall/uspfilter/uspfilter_test.go +++ b/client/firewall/uspfilter/uspfilter_test.go @@ -33,6 +33,10 @@ func (i *IFaceMock) Address() iface.WGAddress { return i.AddressFunc() } +func (i *IFaceMock) Address6() *iface.WGAddress { + return nil +} + func TestManagerCreate(t *testing.T) { ifaceMock := &IFaceMock{ SetFilterFunc: func(iface.PacketFilter) error { return nil }, diff --git a/client/internal/acl/manager.go b/client/internal/acl/manager.go index 05ad6b643f7..91649133980 100644 --- a/client/internal/acl/manager.go +++ b/client/internal/acl/manager.go @@ -16,7 +16,7 @@ import ( mgmProto "github.com/netbirdio/netbird/management/proto" ) -// Manager is a ACL rules manager +// Manager is an ACL rules manager type Manager interface { ApplyFiltering(networkMap *mgmProto.NetworkMap) ResetV6RulesAndAddr() error diff --git a/client/internal/acl/mocks/iface_mapper.go b/client/internal/acl/mocks/iface_mapper.go index 4496f54be37..037b3ad379f 100644 --- a/client/internal/acl/mocks/iface_mapper.go +++ b/client/internal/acl/mocks/iface_mapper.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/netbirdio/netbird/client/internal/acl (interfaces: IFaceMapper) +// Source: ./client/firewall/iface.go // Package mocks is a generated GoMock package. package mocks @@ -7,8 +7,8 @@ package mocks import ( reflect "reflect" + gomock "github.com/golang/mock/gomock" iface "github.com/netbirdio/netbird/iface" - gomock "go.uber.org/mock/gomock" ) // MockIFaceMapper is a mock of IFaceMapper interface. diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index 5dfc171a632..909cb9e1727 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -213,7 +213,7 @@ func TestEngine_UpdateNetworkMap(t *testing.T) { if err != nil { t.Fatal(err) } - engine.wgInterface, err = iface.NewWGIFace("utun102", "100.64.0.1/24", engine.config.WgPort, key.String(), iface.DefaultMTU, newNet, nil) + engine.wgInterface, err = iface.NewWGIFace("utun102", "100.64.0.1/24", "", engine.config.WgPort, key.String(), iface.DefaultMTU, newNet, nil) if err != nil { t.Fatal(err) } @@ -560,6 +560,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) { engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{ WgIfaceName: wgIfaceName, WgAddr: wgAddr, + WgAddr6: "", WgPrivateKey: key, WgPort: 33100, }, MobileDependency{}, peer.NewRecorder("https://mgm")) @@ -567,7 +568,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) { if err != nil { t.Fatal(err) } - engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, engine.config.WgPort, key.String(), iface.DefaultMTU, newNet, nil) + engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, engine.config.WgAddr6, engine.config.WgPort, key.String(), iface.DefaultMTU, newNet, nil) assert.NoError(t, err, "shouldn't return error") input := struct { inputSerial uint64 @@ -729,6 +730,7 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) { engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, &EngineConfig{ WgIfaceName: wgIfaceName, WgAddr: wgAddr, + WgAddr6: "", WgPrivateKey: key, WgPort: 33100, }, MobileDependency{}, peer.NewRecorder("https://mgm")) @@ -736,7 +738,7 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) { if err != nil { t.Fatal(err) } - engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, 33100, key.String(), iface.DefaultMTU, newNet, nil) + engine.wgInterface, err = iface.NewWGIFace(wgIfaceName, wgAddr, engine.config.WgAddr6, 33100, key.String(), iface.DefaultMTU, newNet, nil) assert.NoError(t, err, "shouldn't return error") mockRouteManager := &routemanager.MockManager{ diff --git a/client/internal/routemanager/manager_test.go b/client/internal/routemanager/manager_test.go index 2e5cf6649d8..b5a568d1f5c 100644 --- a/client/internal/routemanager/manager_test.go +++ b/client/internal/routemanager/manager_test.go @@ -405,7 +405,7 @@ func TestManagerUpdateRoutes(t *testing.T) { if err != nil { t.Fatal(err) } - wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun43%d", n), "100.65.65.2/24", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) + wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun43%d", n), "100.65.65.2/24", "", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) require.NoError(t, err, "should create testing WGIface interface") defer wgInterface.Close() diff --git a/client/internal/routemanager/systemops_nonandroid_test.go b/client/internal/routemanager/systemops_nonandroid_test.go index 6f32d9634bc..e153c414e46 100644 --- a/client/internal/routemanager/systemops_nonandroid_test.go +++ b/client/internal/routemanager/systemops_nonandroid_test.go @@ -47,14 +47,14 @@ func TestAddRemoveRoutes(t *testing.T) { if err != nil { t.Fatal(err) } - wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) + wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", "", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) require.NoError(t, err, "should create testing WGIface interface") defer wgInterface.Close() err = wgInterface.Create() require.NoError(t, err, "should create testing wireguard interface") - err = addToRouteTableIfNoExists(testCase.prefix, wgInterface.Address().IP.String()) + err = addToRouteTableIfNoExists(testCase.prefix, wgInterface.Address().IP.String(), wgInterface.Name()) require.NoError(t, err, "addToRouteTableIfNoExists should not return err") prefixGateway, err := getExistingRIBRouteGateway(testCase.prefix) @@ -182,7 +182,7 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) { if err != nil { t.Fatal(err) } - wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) + wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", "", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) require.NoError(t, err, "should create testing WGIface interface") defer wgInterface.Close() @@ -193,12 +193,12 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) { // Prepare the environment if testCase.preExistingPrefix.IsValid() { - err := addToRouteTableIfNoExists(testCase.preExistingPrefix, MockAddr) + err := addToRouteTableIfNoExists(testCase.preExistingPrefix, MockAddr, wgInterface.Name()) require.NoError(t, err, "should not return err when adding pre-existing route") } // Add the route - err = addToRouteTableIfNoExists(testCase.prefix, MockAddr) + err = addToRouteTableIfNoExists(testCase.prefix, MockAddr, wgInterface.Name()) require.NoError(t, err, "should not return err when adding route") if testCase.shouldAddRoute { diff --git a/iface/iface_test.go b/iface/iface_test.go index 3fc25063798..ddb0585e557 100644 --- a/iface/iface_test.go +++ b/iface/iface_test.go @@ -40,7 +40,7 @@ func TestWGIface_UpdateAddr(t *testing.T) { t.Fatal(err) } - iface, err := NewWGIFace(ifaceName, addr, wgPort, key, DefaultMTU, newNet, nil) + iface, err := NewWGIFace(ifaceName, addr, "", wgPort, key, DefaultMTU, newNet, nil) if err != nil { t.Fatal(err) } @@ -102,7 +102,7 @@ func Test_CreateInterface(t *testing.T) { if err != nil { t.Fatal(err) } - iface, err := NewWGIFace(ifaceName, wgIP, 33100, key, DefaultMTU, newNet, nil) + iface, err := NewWGIFace(ifaceName, wgIP, "", 33100, key, DefaultMTU, newNet, nil) if err != nil { t.Fatal(err) } @@ -137,7 +137,7 @@ func Test_Close(t *testing.T) { t.Fatal(err) } - iface, err := NewWGIFace(ifaceName, wgIP, wgPort, key, DefaultMTU, newNet, nil) + iface, err := NewWGIFace(ifaceName, wgIP, "", wgPort, key, DefaultMTU, newNet, nil) if err != nil { t.Fatal(err) } @@ -170,7 +170,7 @@ func Test_ConfigureInterface(t *testing.T) { if err != nil { t.Fatal(err) } - iface, err := NewWGIFace(ifaceName, wgIP, wgPort, key, DefaultMTU, newNet, nil) + iface, err := NewWGIFace(ifaceName, wgIP, "", wgPort, key, DefaultMTU, newNet, nil) if err != nil { t.Fatal(err) } @@ -218,7 +218,7 @@ func Test_UpdatePeer(t *testing.T) { t.Fatal(err) } - iface, err := NewWGIFace(ifaceName, wgIP, 33100, key, DefaultMTU, newNet, nil) + iface, err := NewWGIFace(ifaceName, wgIP, "", 33100, key, DefaultMTU, newNet, nil) if err != nil { t.Fatal(err) } @@ -279,7 +279,7 @@ func Test_RemovePeer(t *testing.T) { t.Fatal(err) } - iface, err := NewWGIFace(ifaceName, wgIP, 33100, key, DefaultMTU, newNet, nil) + iface, err := NewWGIFace(ifaceName, wgIP, "", 33100, key, DefaultMTU, newNet, nil) if err != nil { t.Fatal(err) } @@ -333,7 +333,7 @@ func Test_ConnectPeers(t *testing.T) { t.Fatal(err) } - iface1, err := NewWGIFace(peer1ifaceName, peer1wgIP, peer1wgPort, peer1Key.String(), DefaultMTU, newNet, nil) + iface1, err := NewWGIFace(peer1ifaceName, peer1wgIP, "", peer1wgPort, peer1Key.String(), DefaultMTU, newNet, nil) if err != nil { t.Fatal(err) } @@ -356,7 +356,7 @@ func Test_ConnectPeers(t *testing.T) { if err != nil { t.Fatal(err) } - iface2, err := NewWGIFace(peer2ifaceName, peer2wgIP, peer2wgPort, peer2Key.String(), DefaultMTU, newNet, nil) + iface2, err := NewWGIFace(peer2ifaceName, peer2wgIP, "", peer2wgPort, peer2Key.String(), DefaultMTU, newNet, nil) if err != nil { t.Fatal(err) } diff --git a/management/server/http/accounts_handler_test.go b/management/server/http/accounts_handler_test.go index fd2c4bfcd33..17a409540d6 100644 --- a/management/server/http/accounts_handler_test.go +++ b/management/server/http/accounts_handler_test.go @@ -62,7 +62,7 @@ func TestAccounts_AccountsHandler(t *testing.T) { handler := initAccountsTestData(&server.Account{ Id: accountID, Domain: "hotmail.com", - Network: server.NewNetwork(), + Network: server.NewNetwork(true), Users: map[string]*server.User{ adminUser.Id: adminUser, }, From 1763da2c1790f9794ad5cb74b40ed035ec887b06 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Sat, 13 Jan 2024 01:26:23 +0100 Subject: [PATCH 12/42] Fix some more testing issues --- .../firewall/nftables/manager_linux_test.go | 129 +++++++++++++++++- management/client/client_test.go | 1 + management/server/network.go | 6 +- management/server/peer/peer.go | 2 +- 4 files changed, 129 insertions(+), 9 deletions(-) diff --git a/client/firewall/nftables/manager_linux_test.go b/client/firewall/nftables/manager_linux_test.go index 6b3b9a0e3c6..9acc10e09f9 100644 --- a/client/firewall/nftables/manager_linux_test.go +++ b/client/firewall/nftables/manager_linux_test.go @@ -61,6 +61,7 @@ func TestNftablesManager(t *testing.T) { }, } }, + Address6Func: func() *iface.WGAddress { return nil }, } // just check on the local interface @@ -107,11 +108,9 @@ func TestNftablesManager(t *testing.T) { Register: 1, Data: ifname("lo"), }, - &expr.Payload{ - DestRegister: 1, - Base: expr.PayloadBaseNetworkHeader, - Offset: uint32(9), - Len: uint32(1), + &expr.Meta{ + Key: expr.MetaKeyL4PROTO, + Register: 1, }, &expr.Cmp{ Register: 1, @@ -160,6 +159,126 @@ func TestNftablesManager(t *testing.T) { require.NoError(t, err, "failed to reset") } +func TestNftablesManager6(t *testing.T) { + mock := &iFaceMock{ + NameFunc: func() string { + return "lo" + }, + AddressFunc: func() iface.WGAddress { + return iface.WGAddress{ + IP: net.ParseIP("100.96.0.1"), + Network: &net.IPNet{ + IP: net.ParseIP("100.96.0.0"), + Mask: net.IPv4Mask(255, 255, 255, 0), + }, + } + }, + Address6Func: func() *iface.WGAddress { + return &iface.WGAddress{ + IP: net.ParseIP("2001:db8::0123:4567:890a:bcde"), + Network: &net.IPNet{ + IP: net.ParseIP("2001:db8::"), + Mask: net.CIDRMask(64, 128), + }, + } + }, + } + + // just check on the local interface + manager, err := Create(context.Background(), mock) + require.NoError(t, err) + time.Sleep(time.Second * 3) + + defer func() { + err = manager.Reset() + require.NoError(t, err, "failed to reset") + time.Sleep(time.Second) + }() + + ip := net.ParseIP("2001:db8::fedc:ba09:8765:4321") + + testClient := &nftables.Conn{} + + rule, err := manager.AddFiltering( + ip, + fw.ProtocolTCP, + nil, + &fw.Port{Values: []int{53}}, + fw.RuleDirectionIN, + fw.ActionDrop, + "", + "", + ) + require.NoError(t, err, "failed to add rule") + + err = manager.Flush() + require.NoError(t, err, "failed to flush") + + rules, err := testClient.GetRules(manager.aclManager.workTable6, manager.aclManager.chainInputRules6) + require.NoError(t, err, "failed to get rules") + + require.Len(t, rules, 1, "expected 1 rules") + + ipToAdd, _ := netip.AddrFromSlice(ip) + add := ipToAdd.Unmap() + expectedExprs := []expr.Any{ + &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: ifname("lo"), + }, + &expr.Meta{ + Key: expr.MetaKeyL4PROTO, + Register: 1, + }, + &expr.Cmp{ + Register: 1, + Op: expr.CmpOpEq, + Data: []byte{unix.IPPROTO_TCP}, + }, + &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseNetworkHeader, + Offset: 8, + Len: 16, + }, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: add.AsSlice(), + }, + &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseTransportHeader, + Offset: 2, + Len: 2, + }, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: []byte{0, 53}, + }, + &expr.Verdict{Kind: expr.VerdictDrop}, + } + require.ElementsMatch(t, rules[0].Exprs, expectedExprs, "expected the same expressions") + + for _, r := range rule { + err = manager.DeleteRule(r) + require.NoError(t, err, "failed to delete rule") + } + + err = manager.Flush() + require.NoError(t, err, "failed to flush") + + rules, err = testClient.GetRules(manager.aclManager.workTable6, manager.aclManager.chainInputRules6) + require.NoError(t, err, "failed to get rules") + require.Len(t, rules, 0, "expected 0 rules after deletion") + + err = manager.Reset() + require.NoError(t, err, "failed to reset") +} + func TestNFtablesCreatePerformance(t *testing.T) { mock := &iFaceMock{ NameFunc: func() string { diff --git a/management/client/client_test.go b/management/client/client_test.go index 9ebb58420c1..a513f36cd0b 100644 --- a/management/client/client_test.go +++ b/management/client/client_test.go @@ -352,6 +352,7 @@ func Test_SystemMetaDataFromClient(t *testing.T) { Platform: info.Platform, OS: info.OS, WiretrusteeVersion: info.WiretrusteeVersion, + Ipv6Supported: info.Ipv6Supported, } assert.Equal(t, ValidKey, actualValidKey) diff --git a/management/server/network.go b/management/server/network.go index efc7a5bd606..a0249a6d9ca 100644 --- a/management/server/network.go +++ b/management/server/network.go @@ -40,9 +40,9 @@ type NetworkMap struct { } type Network struct { - Identifier string `json:"id"` - Net net.IPNet `gorm:"serializer:gob"` - Net6 *net.IPNet + Identifier string `json:"id"` + Net net.IPNet `gorm:"serializer:gob"` + Net6 *net.IPNet `gorm:"serializer:gob"` Dns string // Serial is an ID that increments by 1 when any change to the network happened (e.g. new peer has been added). // Used to synchronize state to the client apps. diff --git a/management/server/peer/peer.go b/management/server/peer/peer.go index 07499fb441f..e0fa83ab9e2 100644 --- a/management/server/peer/peer.go +++ b/management/server/peer/peer.go @@ -20,7 +20,7 @@ type Peer struct { // IP address of the Peer IP net.IP `gorm:"uniqueIndex:idx_peers_account_id_ip"` // IPv6 address of the Peer - IP6 *net.IP + IP6 *net.IP `gorm:"uniqueIndex:idx_peers_account_id_ip6"` // Meta is a Peer system meta data Meta PeerSystemMeta `gorm:"embedded;embeddedPrefix:meta_"` // Name is peer's name (machine name) From fc41c782a0142d296cbfedddba44f925081098b9 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Sun, 25 Feb 2024 02:12:01 +0100 Subject: [PATCH 13/42] Fix more CI issues due to IPv6 --- client/firewall/nftables/route_linux.go | 4 ++-- client/internal/acl/manager.go | 5 ++--- client/internal/acl/manager_test.go | 2 ++ client/internal/routemanager/systemops_linux.go | 5 ++++- management/server/policy_test.go | 2 ++ 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/client/firewall/nftables/route_linux.go b/client/firewall/nftables/route_linux.go index f0678d3dfc1..3155f006839 100644 --- a/client/firewall/nftables/route_linux.go +++ b/client/firewall/nftables/route_linux.go @@ -160,7 +160,7 @@ func (r *router) loadFilterTables() (*nftables.Table, *nftables.Table, error) { err = errFilterTableNotFound } - return table4, table6, errFilterTableNotFound + return table4, table6, err } func (r *router) createContainers() error { @@ -497,7 +497,7 @@ func (r *router) cleanUpDefaultForwardRules() error { if err != nil { return err } - rules6 = append(rules, rules6...) + rules = append(rules, rules6...) } } diff --git a/client/internal/acl/manager.go b/client/internal/acl/manager.go index 91649133980..7d3bb918487 100644 --- a/client/internal/acl/manager.go +++ b/client/internal/acl/manager.go @@ -28,7 +28,6 @@ type DefaultManager struct { ipsetCounter int rulesPairs map[string][]firewall.Rule rulesPairs6 map[string][]firewall.Rule - v6Active bool mutex sync.Mutex } @@ -202,10 +201,10 @@ func (d *DefaultManager) protoRuleToFirewallRule( var ip6 *net.IP = nil if r.PeerIP6 != "" { ip6tmp := net.ParseIP(r.PeerIP6) - ip6 = &ip6tmp - if ip6 == nil { + if ip6tmp == nil { return "", nil, nil, fmt.Errorf("invalid IP address, skipping firewall rule") } + ip6 = &ip6tmp } protocol, err := convertToFirewallProtocol(r.Protocol) diff --git a/client/internal/acl/manager_test.go b/client/internal/acl/manager_test.go index 494d54bf256..f31c22cd87c 100644 --- a/client/internal/acl/manager_test.go +++ b/client/internal/acl/manager_test.go @@ -50,6 +50,7 @@ func TestDefaultManager(t *testing.T) { IP: ip, Network: network, }).AnyTimes() + ifaceMock.EXPECT().Address6().Return(nil) // we receive one rule from the management so for testing purposes ignore it fw, err := firewall.NewFirewall(context.Background(), ifaceMock) @@ -343,6 +344,7 @@ func TestDefaultManagerEnableSSHRules(t *testing.T) { IP: ip, Network: network, }).AnyTimes() + ifaceMock.EXPECT().Address6().Return(nil) // we receive one rule from the management so for testing purposes ignore it fw, err := firewall.NewFirewall(context.Background(), ifaceMock) diff --git a/client/internal/routemanager/systemops_linux.go b/client/internal/routemanager/systemops_linux.go index 6c42d78bb38..12667ac7960 100644 --- a/client/internal/routemanager/systemops_linux.go +++ b/client/internal/routemanager/systemops_linux.go @@ -168,8 +168,11 @@ func enableIPForwarding() error { // Do the same for IPv6 bytes, err = os.ReadFile(ipv6ForwardingPath) + if err != nil { + return err + } if len(bytes) > 0 && bytes[0] == 49 { return nil } - return os.WriteFile(ipv6ForwardingPath, []byte("1"), 0644) + return os.WriteFile(ipv6ForwardingPath, []byte("1"), 0644) //nolint:gosec } diff --git a/management/server/policy_test.go b/management/server/policy_test.go index 715e2a8614e..91b4b3dd1db 100644 --- a/management/server/policy_test.go +++ b/management/server/policy_test.go @@ -138,6 +138,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) { epectedFirewallRules := []*FirewallRule{ { PeerIP: "0.0.0.0", + PeerIP6: "::", Direction: firewallRuleDirectionIN, Action: "accept", Protocol: "all", @@ -145,6 +146,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) { }, { PeerIP: "0.0.0.0", + PeerIP6: "::", Direction: firewallRuleDirectionOUT, Action: "accept", Protocol: "all", From ab9f4806b0ed7aa135223736e05c923e93c16f4b Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Mon, 26 Feb 2024 19:52:55 +0100 Subject: [PATCH 14/42] Fix more testing issues --- client/internal/acl/manager_test.go | 14 ++++++++++++-- management/server/peer/peer.go | 4 ++-- management/server/policy_test.go | 1 + 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/client/internal/acl/manager_test.go b/client/internal/acl/manager_test.go index f31c22cd87c..e4e768176a0 100644 --- a/client/internal/acl/manager_test.go +++ b/client/internal/acl/manager_test.go @@ -19,6 +19,7 @@ func TestDefaultManager(t *testing.T) { FirewallRules: []*mgmProto.FirewallRule{ { PeerIP: "10.93.0.1", + PeerIP6: "2001:db8::fedc:ba09:8765:0001", Direction: mgmProto.FirewallRule_OUT, Action: mgmProto.FirewallRule_ACCEPT, Protocol: mgmProto.FirewallRule_TCP, @@ -26,6 +27,7 @@ func TestDefaultManager(t *testing.T) { }, { PeerIP: "10.93.0.2", + PeerIP6: "2001:db8::fedc:ba09:8765:0002", Direction: mgmProto.FirewallRule_OUT, Action: mgmProto.FirewallRule_DROP, Protocol: mgmProto.FirewallRule_UDP, @@ -50,7 +52,14 @@ func TestDefaultManager(t *testing.T) { IP: ip, Network: network, }).AnyTimes() - ifaceMock.EXPECT().Address6().Return(nil) + ip6, network6, err := net.ParseCIDR("2001:db8::fedc:ba09:8765:4321/64") + if err != nil { + t.Fatalf("failed to parse IP address: %v", err) + } + ifaceMock.EXPECT().Address6().Return(&iface.WGAddress{ + IP: ip6, + Network: network6, + }).AnyTimes() // we receive one rule from the management so for testing purposes ignore it fw, err := firewall.NewFirewall(context.Background(), ifaceMock) @@ -84,6 +93,7 @@ func TestDefaultManager(t *testing.T) { networkMap.FirewallRules, &mgmProto.FirewallRule{ PeerIP: "10.93.0.3", + PeerIP6: "2001:db8::fedc:ba09:8765:0003", Direction: mgmProto.FirewallRule_IN, Action: mgmProto.FirewallRule_DROP, Protocol: mgmProto.FirewallRule_ICMP, @@ -344,7 +354,7 @@ func TestDefaultManagerEnableSSHRules(t *testing.T) { IP: ip, Network: network, }).AnyTimes() - ifaceMock.EXPECT().Address6().Return(nil) + ifaceMock.EXPECT().Address6().Return(nil).AnyTimes() // we receive one rule from the management so for testing purposes ignore it fw, err := firewall.NewFirewall(context.Background(), ifaceMock) diff --git a/management/server/peer/peer.go b/management/server/peer/peer.go index 259b939cfd6..7598b1af307 100644 --- a/management/server/peer/peer.go +++ b/management/server/peer/peer.go @@ -44,7 +44,7 @@ type Peer struct { LastLogin time.Time // Indicate ephemeral peer attribute Ephemeral bool - // Geo location based on connection IP + // Geolocation based on connection IP Location Location `gorm:"embedded;embeddedPrefix:location_"` } @@ -89,7 +89,7 @@ type PeerSystemMeta struct { SystemSerialNumber string SystemProductName string SystemManufacturer string - Ipv6Supported bool + Ipv6Supported bool } func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool { diff --git a/management/server/policy_test.go b/management/server/policy_test.go index 581403eab6c..50a66a15d34 100644 --- a/management/server/policy_test.go +++ b/management/server/policy_test.go @@ -665,6 +665,7 @@ func TestAccount_getPeersByPolicyPostureChecks(t *testing.T) { expectedFirewallRules := []*FirewallRule{ { PeerIP: "0.0.0.0", + PeerIP6: "::", Direction: firewallRuleDirectionOUT, Action: "accept", Protocol: "tcp", From 4460f0758e84149feee07cc22ce4f5083f083f05 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Thu, 29 Feb 2024 02:40:09 +0100 Subject: [PATCH 15/42] Add inheritance of IPv6 enablement status from groups --- management/server/account.go | 10 +-- management/server/activity/codes.go | 6 +- management/server/file_store.go | 7 ++ management/server/group.go | 46 +++++++++-- management/server/http/accounts_handler.go | 2 - management/server/http/api/openapi.yml | 21 +++-- management/server/http/api/types.gen.go | 66 +++++++++++++--- management/server/http/groups_handler.go | 40 ++++++---- management/server/http/peers_handler.go | 21 ++++- management/server/peer.go | 90 +++++++++++++++------- management/server/peer/peer.go | 14 ++++ 11 files changed, 239 insertions(+), 84 deletions(-) diff --git a/management/server/account.go b/management/server/account.go index 37822da69aa..134ce4dd52a 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -79,7 +79,7 @@ type AccountManager interface { GetPeers(accountID, userID string) ([]*nbpeer.Peer, error) MarkPeerConnected(peerKey string, connected bool, realIP net.IP) error DeletePeer(accountID, peerID, userID string) error - UpdatePeer(accountID, userID string, peer *nbpeer.Peer, enableV6 bool) (*nbpeer.Peer, error) + UpdatePeer(accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error) GetNetworkMap(peerID string) (*NetworkMap, error) GetPeerNetwork(peerID string) (*Network, error) AddPeer(setupKey, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, *NetworkMap, error) @@ -180,9 +180,6 @@ type Settings struct { // Extra is a dictionary of Account settings Extra *account.ExtraSettings `gorm:"embedded;embeddedPrefix:extra_"` - - // AssignIPv6ByDefault determines whether hosts added to the network get assigned an IPv6 address by default. - AssignIPv6ByDefault bool } // Copy copies the Settings struct @@ -194,7 +191,6 @@ func (s *Settings) Copy() *Settings { JWTGroupsClaimName: s.JWTGroupsClaimName, GroupsPropagationEnabled: s.GroupsPropagationEnabled, JWTAllowGroups: s.JWTAllowGroups, - AssignIPv6ByDefault: s.AssignIPv6ByDefault, } if s.Extra != nil { settings.Extra = s.Extra.Copy() @@ -983,10 +979,6 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, am.checkAndSchedulePeerLoginExpiration(account) } - if oldSettings.AssignIPv6ByDefault != newSettings.AssignIPv6ByDefault { - am.StoreEvent(userID, accountID, accountID, activity.AccountAssignIPv6ByDefaultUpdated, nil) - } - updatedAccount := account.UpdateSettings(newSettings) err = am.Store.SaveAccount(account) if err != nil { diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index 825536f23f8..761a6688e90 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -80,6 +80,10 @@ const ( PeerIPv6Enabled // PeerIPv6Disabled indicates that a user disabled IPv6 for a peer PeerIPv6Disabled + // PeerIPv6InheritEnabled indicates that IPv6 was enabled for a peer due to a change in group memberships. + PeerIPv6InheritEnabled + // PeerIPv6InheritDisabled indicates that IPv6 was disabled for a peer due to a change in group memberships. + PeerIPv6InheritDisabled // PeerRenamed indicates that a user renamed a peer PeerRenamed // PeerLoginExpirationEnabled indicates that a user enabled login expiration of a peer @@ -98,8 +102,6 @@ const ( AccountPeerLoginExpirationDisabled // AccountPeerLoginExpirationDurationUpdated indicates that a user updated peer login expiration duration for the account AccountPeerLoginExpirationDurationUpdated - // AccountAssignIPv6ByDefaultUpdated indicates that a user changed whether new peers get assigned an IPv6 address if supported. - AccountAssignIPv6ByDefaultUpdated // PersonalAccessTokenCreated indicates that a user created a personal access token PersonalAccessTokenCreated // PersonalAccessTokenDeleted indicates that a user deleted a personal access token diff --git a/management/server/file_store.go b/management/server/file_store.go index ad514781fbc..0bc84ab4b0b 100644 --- a/management/server/file_store.go +++ b/management/server/file_store.go @@ -176,6 +176,13 @@ func restore(file string) (*FileStore, error) { } } + // for migration + for _, peer := range account.Peers { + if peer.V6Setting == "" { + peer.V6Setting = "inherit" + } + } + allGroup, err := account.GetGroupAll() if err != nil { log.Errorf("unable to find the All group, this should happen only when migrate from a version that didn't support groups. Error: %v", err) diff --git a/management/server/group.go b/management/server/group.go index be8d3fb0e2d..df374178b16 100644 --- a/management/server/group.go +++ b/management/server/group.go @@ -2,6 +2,7 @@ package server import ( "fmt" + "slices" log "github.com/sirupsen/logrus" @@ -35,6 +36,8 @@ type Group struct { // Peers list of the group Peers []string `gorm:"serializer:json"` + IPv6Enabled bool + IntegrationReference IntegrationReference `gorm:"embedded;embeddedPrefix:integration_ref_"` } @@ -48,6 +51,7 @@ func (g *Group) Copy() *Group { ID: g.ID, Name: g.Name, Issued: g.Issued, + IPv6Enabled: g.IPv6Enabled, Peers: make([]string, len(g.Peers)), IntegrationReference: g.IntegrationReference, } @@ -125,6 +129,40 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G oldGroup, exists := account.Groups[newGroup.ID] account.Groups[newGroup.ID] = newGroup + // Determine peer difference for group. + addedPeers := make([]string, 0) + removedPeers := make([]string, 0) + if exists { + addedPeers = difference(newGroup.Peers, oldGroup.Peers) + removedPeers = difference(oldGroup.Peers, newGroup.Peers) + } else { + addedPeers = append(addedPeers, newGroup.Peers...) + } + + // Need to check whether IPv6 status has changed for all potentially affected peers. + peersToUpdate := removedPeers + if exists && oldGroup.IPv6Enabled != newGroup.IPv6Enabled { + peersToUpdate = slices.Concat(peersToUpdate, newGroup.Peers) + } else { + peersToUpdate = slices.Concat(peersToUpdate, addedPeers) + } + + for _, peer := range peersToUpdate { + peerObj := account.GetPeer(peer) + update, err := am.DeterminePeerV6(userID, account, peerObj) + if err != nil { + return err + } + if update { + account.UpdatePeer(peerObj) + if peerObj.IP6 != nil { + am.StoreEvent(userID, newGroup.ID, accountID, activity.PeerIPv6InheritEnabled, newGroup.EventMeta()) + } else { + am.StoreEvent(userID, newGroup.ID, accountID, activity.PeerIPv6InheritDisabled, newGroup.EventMeta()) + } + } + } + account.Network.IncSerial() if err = am.Store.SaveAccount(account); err != nil { return err @@ -134,13 +172,7 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G // the following snippet tracks the activity and stores the group events in the event store. // It has to happen after all the operations have been successfully performed. - addedPeers := make([]string, 0) - removedPeers := make([]string, 0) - if exists { - addedPeers = difference(newGroup.Peers, oldGroup.Peers) - removedPeers = difference(oldGroup.Peers, newGroup.Peers) - } else { - addedPeers = append(addedPeers, newGroup.Peers...) + if !exists { am.StoreEvent(userID, newGroup.ID, accountID, activity.GroupCreated, newGroup.EventMeta()) } diff --git a/management/server/http/accounts_handler.go b/management/server/http/accounts_handler.go index afd3ba85afa..71088cfaf3f 100644 --- a/management/server/http/accounts_handler.go +++ b/management/server/http/accounts_handler.go @@ -76,7 +76,6 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request) settings := &server.Settings{ PeerLoginExpirationEnabled: req.Settings.PeerLoginExpirationEnabled, PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)), - AssignIPv6ByDefault: req.Settings.AssignIpv6ByDefault, } if req.Settings.Extra != nil { @@ -144,7 +143,6 @@ func toAccountResponse(account *server.Account) *api.Account { JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled, JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName, JwtAllowGroups: &jwtAllowGroups, - AssignIpv6ByDefault: account.Settings.AssignIPv6ByDefault, } if account.Settings.Extra != nil { diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index 4a231defc06..ea85bc471c4 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -56,10 +56,6 @@ components: description: Period of time after which peer login expires (seconds). type: integer example: 43200 - assign_ipv6_by_default: - description: Whether to enable IPv6 for new hosts added to the system - type: boolean - example: false groups_propagation_enabled: description: Allows propagate the new user auto groups to peers that belongs to the user type: boolean @@ -83,7 +79,6 @@ components: required: - peer_login_expiration_enabled - peer_login_expiration - - assign_ipv6_by_default AccountExtraSettings: type: object properties: @@ -240,7 +235,8 @@ components: type: boolean example: true ipv6_enabled: - type: boolean + type: string + enum: [enabled, disabled, inherit] example: false required: - name @@ -316,8 +312,9 @@ components: example: true ipv6_enabled: description: Whether IPv6 is enabled for this peer. - type: boolean - example: true + type: string + enum: [enabled, disabled, inherit] + example: inherit dns_label: description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud type: string @@ -630,8 +627,12 @@ components: items: type: string example: "ch8i4ug6lnn4g9hqv7m1" + ipv6_enabled: + description: Whether IPv6 should be enabled for all members with IPv6 set to "inherit" + type: boolean required: - name + - ipv6_enabled Group: allOf: - $ref: '#/components/schemas/GroupMinimum' @@ -642,8 +643,12 @@ components: type: array items: $ref: '#/components/schemas/PeerMinimum' + ipv6_enabled: + description: Whether IPv6 should be enabled for all members with IPv6 set to "inherit" + type: boolean required: - peers + - ipv6_enabled RuleMinimum: type: object properties: diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index 88eb6709414..be4169e9bd9 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -74,6 +74,34 @@ const ( NameserverNsTypeUdp NameserverNsType = "udp" ) +// Defines values for PeerIpv6Enabled. +const ( + PeerIpv6EnabledDisabled PeerIpv6Enabled = "disabled" + PeerIpv6EnabledEnabled PeerIpv6Enabled = "enabled" + PeerIpv6EnabledInherit PeerIpv6Enabled = "inherit" +) + +// Defines values for PeerBaseIpv6Enabled. +const ( + PeerBaseIpv6EnabledDisabled PeerBaseIpv6Enabled = "disabled" + PeerBaseIpv6EnabledEnabled PeerBaseIpv6Enabled = "enabled" + PeerBaseIpv6EnabledInherit PeerBaseIpv6Enabled = "inherit" +) + +// Defines values for PeerBatchIpv6Enabled. +const ( + PeerBatchIpv6EnabledDisabled PeerBatchIpv6Enabled = "disabled" + PeerBatchIpv6EnabledEnabled PeerBatchIpv6Enabled = "enabled" + PeerBatchIpv6EnabledInherit PeerBatchIpv6Enabled = "inherit" +) + +// Defines values for PeerRequestIpv6Enabled. +const ( + PeerRequestIpv6EnabledDisabled PeerRequestIpv6Enabled = "disabled" + PeerRequestIpv6EnabledEnabled PeerRequestIpv6Enabled = "enabled" + PeerRequestIpv6EnabledInherit PeerRequestIpv6Enabled = "inherit" +) + // Defines values for PolicyRuleAction. const ( PolicyRuleActionAccept PolicyRuleAction = "accept" @@ -167,9 +195,7 @@ type AccountRequest struct { // AccountSettings defines model for AccountSettings. type AccountSettings struct { - // AssignIpv6ByDefault Whether to enable IPv6 for new hosts added to the system - AssignIpv6ByDefault bool `json:"assign_ipv6_by_default"` - Extra *AccountExtraSettings `json:"extra,omitempty"` + Extra *AccountExtraSettings `json:"extra,omitempty"` // GroupsPropagationEnabled Allows propagate the new user auto groups to peers that belongs to the user GroupsPropagationEnabled *bool `json:"groups_propagation_enabled,omitempty"` @@ -285,6 +311,9 @@ type Group struct { // Id Group ID Id string `json:"id"` + // Ipv6Enabled Whether IPv6 should be enabled for all members with IPv6 set to "inherit" + Ipv6Enabled bool `json:"ipv6_enabled"` + // Issued How group was issued by API or from JWT token Issued *string `json:"issued,omitempty"` @@ -315,6 +344,9 @@ type GroupMinimum struct { // GroupRequest defines model for GroupRequest. type GroupRequest struct { + // Ipv6Enabled Whether IPv6 should be enabled for all members with IPv6 set to "inherit" + Ipv6Enabled bool `json:"ipv6_enabled"` + // Name Group name identifier Name string `json:"name"` @@ -478,7 +510,7 @@ type Peer struct { Ip6 *string `json:"ip6,omitempty"` // Ipv6Enabled Whether IPv6 is enabled for this peer. - Ipv6Enabled bool `json:"ipv6_enabled"` + Ipv6Enabled PeerIpv6Enabled `json:"ipv6_enabled"` // Ipv6Supported Whether this peer supports IPv6 Ipv6Supported bool `json:"ipv6_supported"` @@ -517,6 +549,9 @@ type Peer struct { Version string `json:"version"` } +// PeerIpv6Enabled Whether IPv6 is enabled for this peer. +type PeerIpv6Enabled string + // PeerBase defines model for PeerBase. type PeerBase struct { // ApprovalRequired (Cloud only) Indicates whether peer needs approval @@ -556,7 +591,7 @@ type PeerBase struct { Ip6 *string `json:"ip6,omitempty"` // Ipv6Enabled Whether IPv6 is enabled for this peer. - Ipv6Enabled bool `json:"ipv6_enabled"` + Ipv6Enabled PeerBaseIpv6Enabled `json:"ipv6_enabled"` // Ipv6Supported Whether this peer supports IPv6 Ipv6Supported bool `json:"ipv6_supported"` @@ -595,6 +630,9 @@ type PeerBase struct { Version string `json:"version"` } +// PeerBaseIpv6Enabled Whether IPv6 is enabled for this peer. +type PeerBaseIpv6Enabled string + // PeerBatch defines model for PeerBatch. type PeerBatch struct { // AccessiblePeersCount Number of accessible peers @@ -637,7 +675,7 @@ type PeerBatch struct { Ip6 *string `json:"ip6,omitempty"` // Ipv6Enabled Whether IPv6 is enabled for this peer. - Ipv6Enabled bool `json:"ipv6_enabled"` + Ipv6Enabled PeerBatchIpv6Enabled `json:"ipv6_enabled"` // Ipv6Supported Whether this peer supports IPv6 Ipv6Supported bool `json:"ipv6_supported"` @@ -676,6 +714,9 @@ type PeerBatch struct { Version string `json:"version"` } +// PeerBatchIpv6Enabled Whether IPv6 is enabled for this peer. +type PeerBatchIpv6Enabled string + // PeerMinimum defines model for PeerMinimum. type PeerMinimum struct { // Id Peer ID @@ -688,13 +729,16 @@ type PeerMinimum struct { // PeerRequest defines model for PeerRequest. type PeerRequest struct { // ApprovalRequired (Cloud only) Indicates whether peer needs approval - ApprovalRequired *bool `json:"approval_required,omitempty"` - Ipv6Enabled bool `json:"ipv6_enabled"` - LoginExpirationEnabled bool `json:"login_expiration_enabled"` - Name string `json:"name"` - SshEnabled bool `json:"ssh_enabled"` + ApprovalRequired *bool `json:"approval_required,omitempty"` + Ipv6Enabled PeerRequestIpv6Enabled `json:"ipv6_enabled"` + LoginExpirationEnabled bool `json:"login_expiration_enabled"` + Name string `json:"name"` + SshEnabled bool `json:"ssh_enabled"` } +// PeerRequestIpv6Enabled defines model for PeerRequest.Ipv6Enabled. +type PeerRequestIpv6Enabled string + // PersonalAccessToken defines model for PersonalAccessToken. type PersonalAccessToken struct { // CreatedAt Date the token was created diff --git a/management/server/http/groups_handler.go b/management/server/http/groups_handler.go index c06445690db..e2530fb176e 100644 --- a/management/server/http/groups_handler.go +++ b/management/server/http/groups_handler.go @@ -3,6 +3,7 @@ package http import ( "encoding/json" "net/http" + "slices" "github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/util" @@ -78,16 +79,6 @@ func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) { return } - allGroup, err := account.GetGroupAll() - if err != nil { - util.WriteError(err, w) - return - } - if allGroup.ID == groupID { - util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w) - return - } - var req api.PutApiGroupsGroupIdJSONRequestBody err = json.NewDecoder(r.Body).Decode(&req) if err != nil { @@ -106,12 +97,34 @@ func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) { } else { peers = *req.Peers } + + allGroup, err := account.GetGroupAll() + if err != nil { + util.WriteError(err, w) + return + } + + if allGroup.ID == groupID { + if len(peers) != len(allGroup.Peers) { + util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w) + return + } + for _, peer := range peers { + if slices.Contains(allGroup.Peers, peer) { + continue + } + util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w) + return + } + } + group := server.Group{ ID: groupID, Name: req.Name, Peers: peers, Issued: eg.Issued, IntegrationReference: eg.IntegrationReference, + IPv6Enabled: req.Ipv6Enabled, } if err := h.accountManager.SaveGroup(account.Id, user.Id, &group); err != nil { @@ -240,9 +253,10 @@ func (h *GroupsHandler) GetGroup(w http.ResponseWriter, r *http.Request) { func toGroupResponse(account *server.Account, group *server.Group) *api.Group { cache := make(map[string]api.PeerMinimum) gr := api.Group{ - Id: group.ID, - Name: group.Name, - Issued: &group.Issued, + Id: group.ID, + Name: group.Name, + Issued: &group.Issued, + Ipv6Enabled: group.IPv6Enabled, } for _, pid := range group.Peers { diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go index be34b148fcc..7c667555cc9 100644 --- a/management/server/http/peers_handler.go +++ b/management/server/http/peers_handler.go @@ -75,14 +75,19 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe return } + v6Status := nbpeer.V6Inherit + if req.Ipv6Enabled != api.PeerRequestIpv6EnabledInherit { + v6Status = nbpeer.V6Status(req.Ipv6Enabled) + } + update := &nbpeer.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name, - LoginExpirationEnabled: req.LoginExpirationEnabled} + LoginExpirationEnabled: req.LoginExpirationEnabled, V6Setting: v6Status} if req.ApprovalRequired != nil { update.Status = &nbpeer.PeerStatus{RequiresApproval: *req.ApprovalRequired} } - peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update, req.Ipv6Enabled) + peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update) if err != nil { util.WriteError(err, w) return @@ -241,6 +246,10 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD ip6string := peer.IP6.String() ip6 = &ip6string } + v6Status := api.PeerIpv6EnabledInherit + if peer.V6Setting != nbpeer.V6Inherit { + v6Status = api.PeerIpv6Enabled(peer.V6Setting) + } return &api.Peer{ Id: peer.ID, @@ -260,7 +269,7 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD UserId: peer.UserID, UiVersion: peer.Meta.UIVersion, Ipv6Supported: peer.Meta.Ipv6Supported, - Ipv6Enabled: peer.IP6 != nil, + Ipv6Enabled: v6Status, DnsLabel: fqdn(peer, dnsDomain), LoginExpirationEnabled: peer.LoginExpirationEnabled, LastLogin: peer.LastLogin, @@ -282,6 +291,10 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn ip6string := peer.IP6.String() ip6 = &ip6string } + v6Status := api.PeerBatchIpv6EnabledInherit + if peer.V6Setting != nbpeer.V6Inherit { + v6Status = api.PeerBatchIpv6Enabled(peer.V6Setting) + } return &api.PeerBatch{ Id: peer.ID, @@ -301,7 +314,7 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn UserId: peer.UserID, UiVersion: peer.Meta.UIVersion, Ipv6Supported: peer.Meta.Ipv6Supported, - Ipv6Enabled: peer.IP6 != nil, + Ipv6Enabled: v6Status, DnsLabel: fqdn(peer, dnsDomain), LoginExpirationEnabled: peer.LoginExpirationEnabled, LastLogin: peer.LastLogin, diff --git a/management/server/peer.go b/management/server/peer.go index fc80253c5cf..93a9c12208b 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -3,6 +3,7 @@ package server import ( "fmt" "net" + "slices" "strings" "time" @@ -147,8 +148,49 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected return nil } -// UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled, and Peer.LoginExpirationEnabled can be updated. -func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nbpeer.Peer, enableV6 bool) (*nbpeer.Peer, error) { +// Determines the current IPv6 status of the peer (including checks for inheritance) and generates a new or removes an +// existing IPv6 address if necessary. +// Note that this change does not get persisted here. +// +// Returns a boolean that indicates whether the peer changed and needs to be updated in the data source. +func (am *DefaultAccountManager) DeterminePeerV6(userID string, account *Account, peer *nbpeer.Peer) (bool, error) { + v6Setting := peer.V6Setting + if peer.V6Setting == nbpeer.V6Inherit { + if peer.Meta.Ipv6Supported { + for _, group := range account.Groups { + if group.IPv6Enabled && slices.Contains(group.Peers, peer.ID) { + v6Setting = nbpeer.V6Enabled + } + } + } + + if v6Setting == nbpeer.V6Inherit { + v6Setting = nbpeer.V6Disabled + } + } + + if v6Setting == nbpeer.V6Enabled && peer.IP6 == nil { + if !peer.Meta.Ipv6Supported { + return false, status.Errorf(status.PreconditionFailed, "failed allocating new IPv6 for peer %s - peer does not support IPv6", peer.Name) + } + if account.Network.Net6 == nil { + account.Network.Net6 = GenerateNetwork6() + } + v6tmp, err := AllocatePeerIP6(*account.Network.Net6, account.getTakenIP6s()) + if err != nil { + return false, err + } + peer.IP6 = &v6tmp + return true, nil + } else if v6Setting == nbpeer.V6Disabled && peer.IP6 != nil { + peer.IP6 = nil + return true, nil + } + return false, nil +} + +// UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled, Peer.V6Setting and Peer.LoginExpirationEnabled can be updated. +func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nbpeer.Peer) (*nbpeer.Peer, error) { unlock := am.Store.AcquireAccountLock(accountID) defer unlock() @@ -167,22 +209,18 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nb return nil, err } - if enableV6 && peer.IP6 == nil { - if !peer.Meta.Ipv6Supported { - return nil, status.Errorf(status.PreconditionFailed, "failed allocating new IPv6 for peer %s - peer does not support IPv6", peer.Name) - } - if account.Network.Net6 == nil { - account.Network.Net6 = GenerateNetwork6() - } - v6tmp, err := AllocatePeerIP6(*account.Network.Net6, account.getTakenIP6s()) + if peer.V6Setting != update.V6Setting { + peer.V6Setting = update.V6Setting + prevV6 := peer.IP6 + v6StatusChanged, err := am.DeterminePeerV6(userID, account, peer) if err != nil { return nil, err } - peer.IP6 = &v6tmp - am.StoreEvent(userID, peer.IP6.String(), accountID, activity.PeerIPv6Enabled, peer.EventMeta(am.GetDNSDomain())) - } else if !enableV6 && peer.IP6 != nil { - am.StoreEvent(userID, peer.IP6.String(), accountID, activity.PeerIPv6Disabled, peer.EventMeta(am.GetDNSDomain())) - peer.IP6 = nil + if v6StatusChanged && peer.IP6 != nil { + am.StoreEvent(userID, peer.IP6.String(), account.Id, activity.PeerIPv6Enabled, peer.EventMeta(am.GetDNSDomain())) + } else if v6StatusChanged && peer.IP6 == nil { + am.StoreEvent(userID, prevV6.String(), account.Id, activity.PeerIPv6Disabled, peer.EventMeta(am.GetDNSDomain())) + } } if peer.SSHEnabled != update.SSHEnabled { @@ -428,24 +466,12 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P return nil, nil, err } - var nextIp6 *net.IP = nil - if account.Settings.AssignIPv6ByDefault && peer.Meta.Ipv6Supported { - if network.Net6 == nil { - network.Net6 = GenerateNetwork6() - } - nextIp6tmp, err := AllocatePeerIP6(*network.Net6, takenIps) - if err != nil { - return nil, nil, err - } - nextIp6 = &nextIp6tmp - } - newPeer := &nbpeer.Peer{ ID: xid.New().String(), Key: peer.Key, SetupKey: upperKey, IP: nextIp, - IP6: nextIp6, + IP6: nil, Meta: peer.Meta, Name: peer.Meta.Hostname, DNSLabel: newLabel, @@ -456,6 +482,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P LastLogin: time.Now().UTC(), LoginExpirationEnabled: addedByUser, Ephemeral: ephemeral, + V6Setting: "", // corresponds to "inherit from groups" } if account.Settings.Extra != nil { @@ -490,6 +517,11 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P } } + _, err = am.DeterminePeerV6(userID, account, newPeer) + if err != nil { + return nil, nil, err + } + if addedByUser { user, err := account.FindUser(userID) if err != nil { @@ -649,6 +681,8 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw func disableNoLongerSupportedFeatures(peer *nbpeer.Peer) bool { if !peer.Meta.Ipv6Supported && peer.IP6 != nil { peer.IP6 = nil + // Reset V6 setting to default "inherit" so that we maintain consistent state if IPv6 is ever supported again. + peer.V6Setting = nbpeer.V6Inherit return true } return false diff --git a/management/server/peer/peer.go b/management/server/peer/peer.go index 7598b1af307..be2be9727ea 100644 --- a/management/server/peer/peer.go +++ b/management/server/peer/peer.go @@ -46,8 +46,21 @@ type Peer struct { Ephemeral bool // Geolocation based on connection IP Location Location `gorm:"embedded;embeddedPrefix:location_"` + // Whether IPv6 should be enabled or not. + V6Setting V6Status } +type V6Status string + +const ( + // Inherit IPv6 settings from groups (=> if one group the peer is a member of has IPv6 enabled, it will be enabled). + V6Inherit V6Status = "" + // Enable IPv6 regardless of group settings, as long as it is supported. + V6Enabled V6Status = "enabled" + // Disable IPv6 regardless of group settings. + V6Disabled V6Status = "disabled" +) + type PeerStatus struct { // LastSeen is the last time peer was connected to the management service LastSeen time.Time @@ -155,6 +168,7 @@ func (p *Peer) Copy() *Peer { LastLogin: p.LastLogin, Ephemeral: p.Ephemeral, Location: p.Location, + V6Setting: p.V6Setting, } } From 7a0df2cc57a2de9de438baf1763390245befeb4f Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Thu, 29 Feb 2024 03:14:55 +0100 Subject: [PATCH 16/42] Fix IPv6 events not having associated messages --- management/server/activity/codes.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index 761a6688e90..67e5ec9db60 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -184,6 +184,10 @@ var activityMap = map[Activity]Code{ PeerRenamed: {"Peer renamed", "peer.rename"}, PeerLoginExpirationEnabled: {"Peer login expiration enabled", "peer.login.expiration.enable"}, PeerLoginExpirationDisabled: {"Peer login expiration disabled", "peer.login.expiration.disable"}, + PeerIPv6Enabled: {"Peer IPv6 enabled by user", "peer.login.ipv6.manual_enable"}, + PeerIPv6Disabled: {"Peer IPv6 disabled by user", "peer.login.ipv6.manual_disable"}, + PeerIPv6InheritDisabled: {"Peer IPv6 disabled due to change in group settings or membership", "peer.login.ipv6.inherit_disable"}, + PeerIPv6InheritEnabled: {"Peer IPv6 enabled due to change in group settings or membership", "peer.login.ipv6.inherit_enable"}, NameserverGroupCreated: {"Nameserver group created", "nameserver.group.add"}, NameserverGroupDeleted: {"Nameserver group deleted", "nameserver.group.delete"}, NameserverGroupUpdated: {"Nameserver group updated", "nameserver.group.update"}, From dec1850bb99d8fc3be4bbcdf2284dab2235eec8e Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Thu, 29 Feb 2024 17:25:34 +0100 Subject: [PATCH 17/42] Address first review comments regarding IPv6 support --- management/server/activity/codes.go | 24 ++++++++++++------------ management/server/network.go | 4 +++- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index 67e5ec9db60..a2becd4cee8 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -76,14 +76,6 @@ const ( PeerSSHEnabled // PeerSSHDisabled indicates that a user disabled SSH server on a peer PeerSSHDisabled - // PeerIPv6Enabled indicates that a user enabled IPv6 for a peer - PeerIPv6Enabled - // PeerIPv6Disabled indicates that a user disabled IPv6 for a peer - PeerIPv6Disabled - // PeerIPv6InheritEnabled indicates that IPv6 was enabled for a peer due to a change in group memberships. - PeerIPv6InheritEnabled - // PeerIPv6InheritDisabled indicates that IPv6 was disabled for a peer due to a change in group memberships. - PeerIPv6InheritDisabled // PeerRenamed indicates that a user renamed a peer PeerRenamed // PeerLoginExpirationEnabled indicates that a user enabled login expiration of a peer @@ -146,6 +138,14 @@ const ( PostureCheckUpdated // PostureCheckDeleted indicates that the user deleted a posture check PostureCheckDeleted + // PeerIPv6Enabled indicates that a user enabled IPv6 for a peer + PeerIPv6Enabled + // PeerIPv6Disabled indicates that a user disabled IPv6 for a peer + PeerIPv6Disabled + // PeerIPv6InheritEnabled indicates that IPv6 was enabled for a peer due to a change in group memberships. + PeerIPv6InheritEnabled + // PeerIPv6InheritDisabled indicates that IPv6 was disabled for a peer due to a change in group memberships. + PeerIPv6InheritDisabled ) var activityMap = map[Activity]Code{ @@ -184,10 +184,6 @@ var activityMap = map[Activity]Code{ PeerRenamed: {"Peer renamed", "peer.rename"}, PeerLoginExpirationEnabled: {"Peer login expiration enabled", "peer.login.expiration.enable"}, PeerLoginExpirationDisabled: {"Peer login expiration disabled", "peer.login.expiration.disable"}, - PeerIPv6Enabled: {"Peer IPv6 enabled by user", "peer.login.ipv6.manual_enable"}, - PeerIPv6Disabled: {"Peer IPv6 disabled by user", "peer.login.ipv6.manual_disable"}, - PeerIPv6InheritDisabled: {"Peer IPv6 disabled due to change in group settings or membership", "peer.login.ipv6.inherit_disable"}, - PeerIPv6InheritEnabled: {"Peer IPv6 enabled due to change in group settings or membership", "peer.login.ipv6.inherit_enable"}, NameserverGroupCreated: {"Nameserver group created", "nameserver.group.add"}, NameserverGroupDeleted: {"Nameserver group deleted", "nameserver.group.delete"}, NameserverGroupUpdated: {"Nameserver group updated", "nameserver.group.update"}, @@ -216,6 +212,10 @@ var activityMap = map[Activity]Code{ PostureCheckCreated: {"Posture check created", "posture.check.created"}, PostureCheckUpdated: {"Posture check updated", "posture.check.updated"}, PostureCheckDeleted: {"Posture check deleted", "posture.check.deleted"}, + PeerIPv6Enabled: {"Peer IPv6 enabled by user", "peer.login.ipv6.manual_enable"}, + PeerIPv6Disabled: {"Peer IPv6 disabled by user", "peer.login.ipv6.manual_disable"}, + PeerIPv6InheritDisabled: {"Peer IPv6 disabled due to change in group settings or membership", "peer.login.ipv6.inherit_disable"}, + PeerIPv6InheritEnabled: {"Peer IPv6 enabled due to change in group settings or membership", "peer.login.ipv6.inherit_enable"}, } // StringCode returns a string code of the activity diff --git a/management/server/network.go b/management/server/network.go index a0249a6d9ca..56cba34128c 100644 --- a/management/server/network.go +++ b/management/server/network.go @@ -81,7 +81,9 @@ func GenerateNetwork6() *net.IPNet { addrbuf := make([]byte, 16) addrbuf[0] = 0xfd addrbuf[1] = 0x00 - _, _ = r.Read(addrbuf[2:Subnet6Size]) + addrbuf[2] = 0xb1 + addrbuf[3] = 0x4d + _, _ = r.Read(addrbuf[4:Subnet6Size]) n6 := iplib.NewNet6(addrbuf, Subnet6Size*8, 0).IPNet return &n6 From 8de778f2232433a9f891a3ff047b38ecbf20959f Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Thu, 29 Feb 2024 20:51:08 +0100 Subject: [PATCH 18/42] Fix IPv6 table being created even when IPv6 is disabled Also improved stability of IPv6 route and firewall handling on client side --- client/firewall/iptables/manager_linux.go | 6 +- client/firewall/manager/firewall.go | 7 +- client/firewall/nftables/acl_linux.go | 41 +++--- client/firewall/nftables/manager_linux.go | 78 +++++++++-- client/firewall/nftables/route_linux.go | 123 ++++++++++++------ client/firewall/uspfilter/uspfilter.go | 6 +- client/internal/acl/manager.go | 12 +- client/internal/engine.go | 4 +- client/internal/routemanager/manager.go | 17 +++ client/internal/routemanager/server.go | 1 + .../routemanager/server_nonandroid.go | 20 ++- 11 files changed, 231 insertions(+), 84 deletions(-) diff --git a/client/firewall/iptables/manager_linux.go b/client/firewall/iptables/manager_linux.go index 74f1c62a9c1..94d07136d77 100644 --- a/client/firewall/iptables/manager_linux.go +++ b/client/firewall/iptables/manager_linux.go @@ -24,10 +24,14 @@ type Manager struct { router *routerManager } -func (m *Manager) ResetV6RulesAndAddr() error { +func (m *Manager) ResetV6Firewall() error { return nil } +func (m *Manager) V6Active() bool { + return false +} + // iFaceMapper defines subset methods of interface required for manager type iFaceMapper interface { Name() string diff --git a/client/firewall/manager/firewall.go b/client/firewall/manager/firewall.go index d4989c7209c..4e83df47574 100644 --- a/client/firewall/manager/firewall.go +++ b/client/firewall/manager/firewall.go @@ -76,9 +76,12 @@ type Manager interface { // RemoveRoutingRules removes a routing firewall rule RemoveRoutingRules(pair RouterPair) error - // UpdateV6Address makes changes to the firewall to adapt to the IP address changes. + // ResetV6Firewall makes changes to the firewall to adapt to the IP address changes. // It is expected that after calling this method ApplyFiltering will be called to re-add the firewall rules. - ResetV6RulesAndAddr() error + ResetV6Firewall() error + + // V6Active returns whether IPv6 rules should/may be created by upper layers. + V6Active() bool // Reset firewall to the default state Reset() error diff --git a/client/firewall/nftables/acl_linux.go b/client/firewall/nftables/acl_linux.go index f9f022d96fe..f58c19bb07d 100644 --- a/client/firewall/nftables/acl_linux.go +++ b/client/firewall/nftables/acl_linux.go @@ -111,33 +111,40 @@ func newAclManager(table *nftables.Table, table6 *nftables.Table, wgIface iFaceM return m, nil } -// Resets the IPv6 Firewall Table to adapt to changes in IP addresses -func (m *AclManager) UpdateV6Address() error { - for k, r := range m.rules { - if r.ip.To4() == nil { - err := m.DeleteRule(r) - if err != nil { - return err +func (m *AclManager) PrepareV6Reset() (*nftables.Table, error) { + if m.workTable6 != nil { + for k, r := range m.rules { + if r.ip.To4() == nil { + err := m.DeleteRule(r) + if err != nil { + return nil, err + } + delete(m.rules, k) } - delete(m.rules, k) } - } - sets, err := m.rConn.GetSets(m.workTable6) - if err != nil { - for _, set := range sets { - m.rConn.DelSet(set) + sets, err := m.rConn.GetSets(m.workTable6) + if err != nil { + for _, set := range sets { + m.rConn.DelSet(set) + } } + m.ipsetStore6 = newIpsetStore() } - m.ipsetStore6 = newIpsetStore() - m.rConn.FlushTable(m.workTable6) + m.v6Active = m.wgIface.Address6() != nil + + return m.workTable6, nil +} + +func (m *AclManager) ReinitAfterV6Reset(workTable6 *nftables.Table) error { if m.wgIface.Address6() != nil { + m.workTable6 = workTable6 err := m.createDefaultChains6() if err != nil { return err } + } else { + m.workTable6 = nil } - m.v6Active = m.wgIface.Address6() != nil - return nil } diff --git a/client/firewall/nftables/manager_linux.go b/client/firewall/nftables/manager_linux.go index af495aba0ad..c047fb9e91f 100644 --- a/client/firewall/nftables/manager_linux.go +++ b/client/firewall/nftables/manager_linux.go @@ -36,11 +36,19 @@ func Create(context context.Context, wgIface iFaceMapper) (*Manager, error) { wgIface: wgIface, } - workTable, workTable6, err := m.createWorkTables() + workTable, err := m.createWorkTable() if err != nil { return nil, err } + var workTable6 *nftables.Table + if wgIface.Address6() != nil { + workTable6, err = m.createWorkTable6() + if err != nil { + return nil, err + } + } + m.router, err = newRouter(context, workTable, workTable6) if err != nil { return nil, err @@ -55,13 +63,43 @@ func Create(context context.Context, wgIface iFaceMapper) (*Manager, error) { } // Resets the IPv6 Firewall Table to adapt to changes in IP addresses -func (m *Manager) ResetV6RulesAndAddr() error { - err := m.aclManager.UpdateV6Address() +func (m *Manager) ResetV6Firewall() error { + + workTable6, err := m.aclManager.PrepareV6Reset() + if err != nil { + return err + } + + if m.wgIface.Address6() != nil { + if workTable6 != nil { + m.rConn.FlushTable(workTable6) + } else { + workTable6, err = m.createWorkTable6() + m.rConn.Flush() + if err != nil { + return err + } + } + } else { + m.rConn.DelTable(workTable6) + workTable6 = nil + } + + err = m.router.RestoreAfterV6Reset(workTable6) + if err != nil { + return err + } + + err = m.aclManager.ReinitAfterV6Reset(workTable6) if err != nil { return err } - return m.router.UpdateV6Address() + return m.rConn.Flush() +} + +func (m *Manager) V6Active() bool { + return m.aclManager.v6Active } // AddFiltering rule to the firewall @@ -81,10 +119,10 @@ func (m *Manager) AddFiltering( m.mutex.Lock() defer m.mutex.Unlock() - //rawIP := ip.To4() - //if rawIP == nil { - // return nil, fmt.Errorf("unsupported IP version: %s", ip.String()) - //} + rawIP := ip.To4() + if rawIP == nil && m.wgIface.Address6() == nil { + return nil, fmt.Errorf("unsupported IP version: %s", ip.String()) + } return m.aclManager.AddFiltering(ip, proto, sPort, dPort, direction, action, ipsetName, comment) } @@ -221,26 +259,38 @@ func (m *Manager) Flush() error { return m.aclManager.Flush() } -func (m *Manager) createWorkTables() (*nftables.Table, *nftables.Table, error) { +func (m *Manager) createWorkTable() (*nftables.Table, error) { tables, err := m.rConn.ListTablesOfFamily(nftables.TableFamilyIPv4) if err != nil { - return nil, nil, fmt.Errorf("list of tables: %w", err) + return nil, fmt.Errorf("list of tables: %w", err) + } + + for _, t := range tables { + if t.Name == tableName { + m.rConn.DelTable(t) + } } + + table := m.rConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv4}) + err = m.rConn.Flush() + return table, err +} + +func (m *Manager) createWorkTable6() (*nftables.Table, error) { tables6, err := m.rConn.ListTablesOfFamily(nftables.TableFamilyIPv6) if err != nil { - return nil, nil, fmt.Errorf("list of v6 tables: %w", err) + return nil, fmt.Errorf("list of v6 tables: %w", err) } - for _, t := range append(tables, tables6...) { + for _, t := range tables6 { if t.Name == tableName { m.rConn.DelTable(t) } } - table := m.rConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv4}) table6 := m.rConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv6}) err = m.rConn.Flush() - return table, table6, err + return table6, err } func (m *Manager) applyAllowNetbirdRules(chain *nftables.Chain) { diff --git a/client/firewall/nftables/route_linux.go b/client/firewall/nftables/route_linux.go index 3155f006839..76325b0e312 100644 --- a/client/firewall/nftables/route_linux.go +++ b/client/firewall/nftables/route_linux.go @@ -50,9 +50,10 @@ type router struct { chains map[string]*nftables.Chain chains6 map[string]*nftables.Chain // rules is useful to avoid duplicates and to get missing attributes that we don't have when adding new rules - rules map[string]*nftables.Rule - rules6 map[string]*nftables.Rule - isDefaultFwdRulesEnabled bool + rules map[string]*nftables.Rule + rules6 map[string]*nftables.Rule + isDefaultFwdRulesEnabled bool + isDefaultFwdRulesEnabled6 bool } func newRouter(parentCtx context.Context, workTable *nftables.Table, workTable6 *nftables.Table) (*router, error) { @@ -85,14 +86,21 @@ func newRouter(parentCtx context.Context, workTable *nftables.Table, workTable6 log.Errorf("failed to clean up rules from FORWARD chain: %s", err) } + err = r.cleanUpDefaultForwardRules6() + if err != nil { + log.Errorf("failed to clean up rules from IPv6 FORWARD chain: %s", err) + } + err = r.createContainers() if err != nil { log.Errorf("failed to create containers for route: %s", err) } - err = r.createContainers6() - if err != nil { - log.Errorf("failed to create v6 containers for route: %s", err) + if r.workTable6 != nil { + err = r.createContainers6() + if err != nil { + log.Errorf("failed to create v6 containers for route: %s", err) + } } return r, err @@ -110,22 +118,30 @@ func (r *router) ResetForwardRules() { } } -func (r *router) UpdateV6Address() error { - err := r.createContainers6() - if err != nil { - return err - } +func (r *router) RestoreAfterV6Reset(newWorktable6 *nftables.Table) error { + r.workTable6 = newWorktable6 + if newWorktable6 != nil { - for name, rule := range r.rules6 { - rule = &nftables.Rule{ - Table: r.workTable6, - Chain: r.chains6[rule.Chain.Name], - Exprs: rule.Exprs, - UserData: rule.UserData, + err := r.cleanUpDefaultForwardRules6() + if err != nil { + log.Errorf("failed to clean up rules from IPv6 FORWARD chain: %s", err) + } + + err = r.createContainers6() + if err != nil { + return err } - r.rules6[name] = r.conn.AddRule(rule) - } + for name, rule := range r.rules6 { + rule = &nftables.Rule{ + Table: r.workTable6, + Chain: r.chains6[rule.Chain.Name], + Exprs: rule.Exprs, + UserData: rule.UserData, + } + r.rules6[name] = r.conn.AddRule(rule) + } + } return r.conn.Flush() } @@ -218,6 +234,12 @@ func (r *router) createContainers6() error { // InsertRoutingRules inserts a nftable rule pair to the forwarding chain and if enabled, to the nat chain func (r *router) InsertRoutingRules(pair manager.RouterPair) error { + parsedIp, _, _ := net.ParseCIDR(pair.Source) + + if parsedIp.To4() == nil && r.workTable6 == nil { + return fmt.Errorf("nftables: attempted to add IPv6 routing rule even though IPv6 is not enabled for this host") + } + err := r.refreshRulesMap() if err != nil { return err @@ -244,7 +266,6 @@ func (r *router) InsertRoutingRules(pair manager.RouterPair) error { } filterTable := r.filterTable - parsedIp, _, _ := net.ParseCIDR(pair.Source) if parsedIp.To4() == nil { filterTable = r.filterTable6 } @@ -350,11 +371,20 @@ func (r *router) acceptForwardRule(sourceNetwork string) { UserData: []byte(userDataAcceptForwardRuleDst), } r.conn.AddRule(rule) - r.isDefaultFwdRulesEnabled = true + if parsedIp.To4() == nil { + r.isDefaultFwdRulesEnabled6 = true + } else { + r.isDefaultFwdRulesEnabled = true + } } // RemoveRoutingRules removes a nftable rule pair from forwarding and nat chains func (r *router) RemoveRoutingRules(pair manager.RouterPair) error { + parsedIp, _, _ := net.ParseCIDR(pair.Source) + if parsedIp.To4() == nil && r.workTable6 == nil { + return fmt.Errorf("nftables: attempted to remove IPv6 routing rule even though IPv6 is not enabled for this host") + } + err := r.refreshRulesMap() if err != nil { return err @@ -478,28 +508,43 @@ func (r *router) cleanUpDefaultForwardRules() error { } } - if r.filterTable6 != nil { - - chains, err = r.conn.ListChainsOfTableFamily(nftables.TableFamilyIPv6) - if err != nil { - return err - } - - for _, chain := range chains { - if chain.Table.Name != r.filterTable6.Name { - continue - } - if chain.Name != "FORWARD" { - continue - } - - rules6, err := r.conn.GetRules(r.filterTable, chain) + for _, rule := range rules { + if bytes.Equal(rule.UserData, []byte(userDataAcceptForwardRuleSrc)) || bytes.Equal(rule.UserData, []byte(userDataAcceptForwardRuleDst)) { + err := r.conn.DelRule(rule) if err != nil { return err } - rules = append(rules, rules6...) } } + r.isDefaultFwdRulesEnabled = false + return r.conn.Flush() +} +func (r *router) cleanUpDefaultForwardRules6() error { + if r.filterTable == nil { + r.isDefaultFwdRulesEnabled = false + return nil + } + + chains, err := r.conn.ListChainsOfTableFamily(nftables.TableFamilyIPv6) + if err != nil { + return err + } + + var rules []*nftables.Rule + for _, chain := range chains { + if chain.Table.Name != r.filterTable6.Name { + continue + } + if chain.Name != "FORWARD" { + continue + } + + rules6, err := r.conn.GetRules(r.filterTable6, chain) + if err != nil { + return err + } + rules = rules6 + } for _, rule := range rules { if bytes.Equal(rule.UserData, []byte(userDataAcceptForwardRuleSrc)) || bytes.Equal(rule.UserData, []byte(userDataAcceptForwardRuleDst)) { @@ -509,7 +554,7 @@ func (r *router) cleanUpDefaultForwardRules() error { } } } - r.isDefaultFwdRulesEnabled = false + r.isDefaultFwdRulesEnabled6 = false return r.conn.Flush() } diff --git a/client/firewall/uspfilter/uspfilter.go b/client/firewall/uspfilter/uspfilter.go index 5baf8be1173..2103639e302 100644 --- a/client/firewall/uspfilter/uspfilter.go +++ b/client/firewall/uspfilter/uspfilter.go @@ -70,10 +70,14 @@ func CreateWithNativeFirewall(iface IFaceMapper, nativeFirewall firewall.Manager return mgr, nil } -func (m *Manager) ResetV6RulesAndAddr() error { +func (m *Manager) ResetV6Firewall() error { return nil } +func (m *Manager) V6Active() bool { + return false +} + func create(iface IFaceMapper) (*Manager, error) { m := &Manager{ decoders: sync.Pool{ diff --git a/client/internal/acl/manager.go b/client/internal/acl/manager.go index 7d3bb918487..f2adc1b18c3 100644 --- a/client/internal/acl/manager.go +++ b/client/internal/acl/manager.go @@ -19,7 +19,7 @@ import ( // Manager is an ACL rules manager type Manager interface { ApplyFiltering(networkMap *mgmProto.NetworkMap) - ResetV6RulesAndAddr() error + ResetV6Acl() error } // DefaultManager uses firewall manager to handle @@ -39,7 +39,7 @@ func NewDefaultManager(fm firewall.Manager) *DefaultManager { } } -func (d *DefaultManager) ResetV6RulesAndAddr() error { +func (d *DefaultManager) ResetV6Acl() error { for _, rules := range d.rulesPairs6 { for _, r := range rules { err := d.firewall.DeleteRule(r) @@ -48,7 +48,7 @@ func (d *DefaultManager) ResetV6RulesAndAddr() error { } } } - err := d.firewall.ResetV6RulesAndAddr() + err := d.firewall.ResetV6Firewall() if err != nil { return err } @@ -199,7 +199,7 @@ func (d *DefaultManager) protoRuleToFirewallRule( } var ip6 *net.IP = nil - if r.PeerIP6 != "" { + if d.firewall.V6Active() && r.PeerIP6 != "" { ip6tmp := net.ParseIP(r.PeerIP6) if ip6tmp == nil { return "", nil, nil, fmt.Errorf("invalid IP address, skipping firewall rule") @@ -235,7 +235,7 @@ func (d *DefaultManager) protoRuleToFirewallRule( if rulesPair, ok := d.rulesPairs[ruleID]; ok { rules = rulesPair } - if rulesPair6, ok := d.rulesPairs6[ruleID]; ok && ip6 != nil { + if rulesPair6, ok := d.rulesPairs6[ruleID]; d.firewall.V6Active() && ok && ip6 != nil { rules6 = rulesPair6 } @@ -254,7 +254,7 @@ func (d *DefaultManager) protoRuleToFirewallRule( return "", nil, nil, err } - if ip6 != nil && rules6 == nil { + if d.firewall.V6Active() && ip6 != nil && rules6 == nil { switch r.Direction { case mgmProto.FirewallRule_IN: rules6, err = d.addInRules(*ip6, protocol, port, action, ipsetName, "") diff --git a/client/internal/engine.go b/client/internal/engine.go index 9a937cbeba7..a26032d9680 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -568,10 +568,12 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error { } e.config.WgAddr6 = conf.Address6 - err = e.acl.ResetV6RulesAndAddr() + err = e.acl.ResetV6Acl() if err != nil { return err } + + e.routeManager.ResetV6Routes() log.Infof("updated peer IPv6 address from %s to %s", oldAddr, conf.Address6) } diff --git a/client/internal/routemanager/manager.go b/client/internal/routemanager/manager.go index e8a4bd1341f..d9e22b80442 100644 --- a/client/internal/routemanager/manager.go +++ b/client/internal/routemanager/manager.go @@ -20,6 +20,7 @@ type Manager interface { UpdateRoutes(updateSerial uint64, newRoutes []*route.Route) error SetRouteChangeListener(listener listener.NetworkChangeListener) InitialRouteRange() []string + ResetV6Routes() EnableServerRouter(firewall firewall.Manager) error Stop() } @@ -100,6 +101,22 @@ func (m *DefaultManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Ro } } +// ResetV6Routes deletes all IPv6 routes (necessary if IPv6 address changes). +// It is expected that UpdateRoute is called afterwards to recreate the routing table. +func (m *DefaultManager) ResetV6Routes() { + for id, client := range m.clientNetworks { + if client.network.Addr().Is6() { + log.Debugf("stopping client network watcher due to IPv6 address change, %s", id) + client.stop() + delete(m.clientNetworks, id) + } + } + + if m.serverRouter != nil { + m.serverRouter.handleV6FirewallReset() + } +} + // SetRouteChangeListener set RouteListener for route change notifier func (m *DefaultManager) SetRouteChangeListener(listener listener.NetworkChangeListener) { m.notifier.setListener(listener) diff --git a/client/internal/routemanager/server.go b/client/internal/routemanager/server.go index c9a13a90414..99ff44903d8 100644 --- a/client/internal/routemanager/server.go +++ b/client/internal/routemanager/server.go @@ -5,5 +5,6 @@ import "github.com/netbirdio/netbird/route" type serverRouter interface { updateRoutes(map[string]*route.Route) error removeFromServerNetwork(*route.Route) error + handleV6FirewallReset() cleanUp() } diff --git a/client/internal/routemanager/server_nonandroid.go b/client/internal/routemanager/server_nonandroid.go index 94e0a823b83..e1cb5ab38a2 100644 --- a/client/internal/routemanager/server_nonandroid.go +++ b/client/internal/routemanager/server_nonandroid.go @@ -76,7 +76,13 @@ func (m *defaultServerRouter) updateRoutes(routesMap map[string]*route.Route) er return nil } -func (m *defaultServerRouter) removeFromServerNetwork(route *route.Route) error { +// Handles a reset of the IPv6 firewall table (necessary if IPv6 address changes). +func (m *defaultServerRouter) handleV6FirewallReset() { + m.mux.Lock() + defer m.mux.Unlock() +} + +func (m *defaultServerRouter) removeFromServerNetwork(rt *route.Route) error { select { case <-m.ctx.Done(): log.Infof("not removing from server network because context is done") @@ -84,11 +90,18 @@ func (m *defaultServerRouter) removeFromServerNetwork(route *route.Route) error default: m.mux.Lock() defer m.mux.Unlock() - err := m.firewall.RemoveRoutingRules(routeToRouterPair(m.wgInterface.Address().String(), route)) + routingAddress := m.wgInterface.Address().String() + if rt.NetworkType == route.IPv6Network { + if m.wgInterface.Address6() == nil { + return fmt.Errorf("attempted to add route for IPv6 even though device has no v6 address") + } + routingAddress = m.wgInterface.Address6().String() + } + err := m.firewall.RemoveRoutingRules(routeToRouterPair(routingAddress, rt)) if err != nil { return err } - delete(m.routes, route.ID) + delete(m.routes, rt.ID) return nil } } @@ -125,6 +138,7 @@ func (m *defaultServerRouter) cleanUp() { if r.NetworkType == route.IPv6Network { if m.wgInterface.Address6() == nil { log.Errorf("attempted to remove route for IPv6 even though device has no v6 address") + continue } routingAddress = m.wgInterface.Address6().String() } From a4d0ee1d17e5026768f0da5963d79dcde5135ac3 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Thu, 29 Feb 2024 21:28:52 +0100 Subject: [PATCH 19/42] Fix IPv6 routes not being removed --- client/internal/routemanager/client.go | 2 +- .../internal/routemanager/systemops_linux.go | 25 +++++++++++++++---- .../routemanager/systemops_nonandroid.go | 4 +-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/client/internal/routemanager/client.go b/client/internal/routemanager/client.go index b42e4400e0c..ed566845791 100644 --- a/client/internal/routemanager/client.go +++ b/client/internal/routemanager/client.go @@ -180,7 +180,7 @@ func (c *clientNetwork) removeRouteFromPeerAndSystem() error { if err != nil { return err } - err = removeFromRouteTableIfNonSystem(c.network, c.chosenIP.String()) + err = removeFromRouteTableIfNonSystem(c.network, c.chosenIP.String(), c.wgInterface.Name()) if err != nil { return fmt.Errorf("couldn't remove route %s from system, err: %v", c.network, err) diff --git a/client/internal/routemanager/systemops_linux.go b/client/internal/routemanager/systemops_linux.go index 12667ac7960..4010c6af928 100644 --- a/client/internal/routemanager/systemops_linux.go +++ b/client/internal/routemanager/systemops_linux.go @@ -76,7 +76,7 @@ func addToRouteTable(prefix netip.Prefix, addr string, devName string) error { return nil } -func removeFromRouteTable(prefix netip.Prefix, addr string) error { +func removeFromRouteTable(prefix netip.Prefix, addr string, devName string) error { _, ipNet, err := net.ParseCIDR(prefix.String()) if err != nil { return err @@ -87,15 +87,30 @@ func removeFromRouteTable(prefix netip.Prefix, addr string) error { addrMask = "/128" } - ip, _, err := net.ParseCIDR(addr + addrMask) + var ip net.IP = nil + if addr != "" { + parsedIp, _, err := net.ParseCIDR(addr + addrMask) + if err != nil { + return err + } + // for IPv6, setting the local IP as the gateway address results in an "invalid argument" error. + // Therefore, we cannot use it to obtain the interface for the route (that would only be possible in IPv4). + if parsedIp.To4() != nil { + ip = parsedIp + } + } + + // We obtain the route interface using the device name. + linkAlias, err := netlink.LinkByName(devName) if err != nil { return err } route := &netlink.Route{ - Scope: netlink.SCOPE_UNIVERSE, - Dst: ipNet, - Gw: ip, + Scope: netlink.SCOPE_UNIVERSE, + Dst: ipNet, + Gw: ip, + LinkIndex: linkAlias.Attrs().Index, } err = netlink.RouteDel(route) diff --git a/client/internal/routemanager/systemops_nonandroid.go b/client/internal/routemanager/systemops_nonandroid.go index 24eb8eb8238..ec0fcc9b05e 100644 --- a/client/internal/routemanager/systemops_nonandroid.go +++ b/client/internal/routemanager/systemops_nonandroid.go @@ -110,8 +110,8 @@ func isSubRange(prefix netip.Prefix) (bool, error) { return false, nil } -func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string) error { - return removeFromRouteTable(prefix, addr) +func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string, devName string) error { + return removeFromRouteTable(prefix, addr, devName) } func getExistingRIBRouteGateway(prefix netip.Prefix) (net.IP, error) { From 9df5357debc5788a8df62a8fa66acff09482e79f Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Thu, 29 Feb 2024 22:22:31 +0100 Subject: [PATCH 20/42] Fix DNS IPv6 issues, limit IPv6 nameservers to IPv6 peers --- client/internal/dns/server.go | 6 +++++- management/server/dns.go | 18 +++++++++++------- management/server/grpcserver.go | 2 +- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/client/internal/dns/server.go b/client/internal/dns/server.go index 9986f632ec5..0e621f73e94 100644 --- a/client/internal/dns/server.go +++ b/client/internal/dns/server.go @@ -451,7 +451,11 @@ func (s *DefaultServer) updateLocalResolver(update map[string]nbdns.SimpleRecord } func getNSHostPort(ns nbdns.NameServer) string { - return fmt.Sprintf("%s:%d", ns.IP.String(), ns.Port) + if ns.IP.Is4() { + return fmt.Sprintf("%s:%d", ns.IP.String(), ns.Port) + } else { + return fmt.Sprintf("[%s]:%d", ns.IP.String(), ns.Port) + } } // upstreamCallbacks returns two functions, the first one is used to deactivate diff --git a/management/server/dns.go b/management/server/dns.go index f6e3531ec56..7cd171d3e4c 100644 --- a/management/server/dns.go +++ b/management/server/dns.go @@ -112,7 +112,7 @@ func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string return nil } -func toProtocolDNSConfig(update nbdns.Config) *proto.DNSConfig { +func toProtocolDNSConfig(update nbdns.Config, ipv6Enabled bool) *proto.DNSConfig { protoUpdate := &proto.DNSConfig{ServiceEnable: update.ServiceEnable} for _, zone := range update.CustomZones { @@ -136,14 +136,18 @@ func toProtocolDNSConfig(update nbdns.Config) *proto.DNSConfig { SearchDomainsEnabled: nsGroup.SearchDomainsEnabled, } for _, ns := range nsGroup.NameServers { - protoNS := &proto.NameServer{ - IP: ns.IP.String(), - Port: int64(ns.Port), - NSType: int64(ns.NSType), + if ns.IP.Is4() || ipv6Enabled { + protoNS := &proto.NameServer{ + IP: ns.IP.String(), + Port: int64(ns.Port), + NSType: int64(ns.NSType), + } + protoGroup.NameServers = append(protoGroup.NameServers, protoNS) } - protoGroup.NameServers = append(protoGroup.NameServers, protoNS) } - protoUpdate.NameServerGroups = append(protoUpdate.NameServerGroups, protoGroup) + if len(protoGroup.NameServers) > 0 { + protoUpdate.NameServerGroups = append(protoUpdate.NameServerGroups, protoGroup) + } } return protoUpdate diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index c74259475b8..e9ae735b67c 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -489,7 +489,7 @@ func toSyncResponse(config *Config, peer *nbpeer.Peer, turnCredentials *TURNCred routesUpdate := toProtocolRoutes(networkMap.Routes) - dnsUpdate := toProtocolDNSConfig(networkMap.DNSConfig) + dnsUpdate := toProtocolDNSConfig(networkMap.DNSConfig, peer.IP6 != nil) offlinePeers := toRemotePeerConfig(networkMap.OfflinePeers, dnsName, peer.IP6 != nil) From 5f15d248fb57d5a0e30ebdde4653e9dbae48f09c Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Thu, 29 Feb 2024 23:12:56 +0100 Subject: [PATCH 21/42] Improve code for IPv6 DNS server selection, add AAAA custom records --- management/server/account.go | 2 +- management/server/dns.go | 45 +++++++++++++++++++++++---------- management/server/grpcserver.go | 2 +- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/management/server/account.go b/management/server/account.go index 134ce4dd52a..46cf04e959f 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -430,7 +430,7 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { if dnsManagementStatus { var zones []nbdns.CustomZone - peersCustomZone := getPeersCustomZone(a, dnsDomain) + peersCustomZone := getPeersCustomZone(a, dnsDomain, peer.IP6 != nil) if peersCustomZone.Domain != "" { zones = append(zones, peersCustomZone) } diff --git a/management/server/dns.go b/management/server/dns.go index 7cd171d3e4c..ce1c02c9419 100644 --- a/management/server/dns.go +++ b/management/server/dns.go @@ -112,7 +112,7 @@ func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string return nil } -func toProtocolDNSConfig(update nbdns.Config, ipv6Enabled bool) *proto.DNSConfig { +func toProtocolDNSConfig(update nbdns.Config) *proto.DNSConfig { protoUpdate := &proto.DNSConfig{ServiceEnable: update.ServiceEnable} for _, zone := range update.CustomZones { @@ -136,24 +136,20 @@ func toProtocolDNSConfig(update nbdns.Config, ipv6Enabled bool) *proto.DNSConfig SearchDomainsEnabled: nsGroup.SearchDomainsEnabled, } for _, ns := range nsGroup.NameServers { - if ns.IP.Is4() || ipv6Enabled { - protoNS := &proto.NameServer{ - IP: ns.IP.String(), - Port: int64(ns.Port), - NSType: int64(ns.NSType), - } - protoGroup.NameServers = append(protoGroup.NameServers, protoNS) + protoNS := &proto.NameServer{ + IP: ns.IP.String(), + Port: int64(ns.Port), + NSType: int64(ns.NSType), } + protoGroup.NameServers = append(protoGroup.NameServers, protoNS) } - if len(protoGroup.NameServers) > 0 { - protoUpdate.NameServerGroups = append(protoUpdate.NameServerGroups, protoGroup) - } + protoUpdate.NameServerGroups = append(protoUpdate.NameServerGroups, protoGroup) } return protoUpdate } -func getPeersCustomZone(account *Account, dnsDomain string) nbdns.CustomZone { +func getPeersCustomZone(account *Account, dnsDomain string, enableIPv6 bool) nbdns.CustomZone { if dnsDomain == "" { log.Errorf("no dns domain is set, returning empty zone") return nbdns.CustomZone{} @@ -176,6 +172,16 @@ func getPeersCustomZone(account *Account, dnsDomain string) nbdns.CustomZone { TTL: defaultTTL, RData: peer.IP.String(), }) + + if peer.IP6 != nil && enableIPv6 { + customZone.Records = append(customZone.Records, nbdns.SimpleRecord{ + Name: dns.Fqdn(peer.DNSLabel + "." + dnsDomain), + Type: int(dns.TypeAAAA), + Class: nbdns.DefaultClass, + TTL: defaultTTL, + RData: peer.IP6.String(), + }) + } } return customZone @@ -183,6 +189,7 @@ func getPeersCustomZone(account *Account, dnsDomain string) nbdns.CustomZone { func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup { groupList := account.getPeerGroups(peerID) + peer := account.GetPeer(peerID) var peerNSGroups []*nbdns.NameServerGroup @@ -193,8 +200,18 @@ func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup { for _, gID := range nsGroup.Groups { _, found := groupList[gID] if found { - if !peerIsNameserver(account.GetPeer(peerID), nsGroup) { - peerNSGroups = append(peerNSGroups, nsGroup.Copy()) + if !peerIsNameserver(peer, nsGroup) { + filteredNsGroup := nsGroup.Copy() + var newNameserverList []nbdns.NameServer + for _, nameserver := range filteredNsGroup.NameServers { + if nameserver.IP.Is4() || peer.IP6 != nil { + newNameserverList = append(newNameserverList, nameserver) + } + } + if len(newNameserverList) > 0 { + filteredNsGroup.NameServers = newNameserverList + peerNSGroups = append(peerNSGroups, filteredNsGroup) + } break } } diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index e9ae735b67c..c74259475b8 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -489,7 +489,7 @@ func toSyncResponse(config *Config, peer *nbpeer.Peer, turnCredentials *TURNCred routesUpdate := toProtocolRoutes(networkMap.Routes) - dnsUpdate := toProtocolDNSConfig(networkMap.DNSConfig, peer.IP6 != nil) + dnsUpdate := toProtocolDNSConfig(networkMap.DNSConfig) offlinePeers := toRemotePeerConfig(networkMap.OfflinePeers, dnsName, peer.IP6 != nil) From f05ef7da823a7ced364d1a2927b4024f7cb7a7f4 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Fri, 1 Mar 2024 01:35:25 +0100 Subject: [PATCH 22/42] Ensure IPv6 routes can only exist for IPv6 routing peers --- management/server/account.go | 10 +++- management/server/activity/codes.go | 10 ++-- management/server/file_store.go | 7 --- management/server/group.go | 2 +- management/server/http/api/openapi.yml | 12 ++-- management/server/http/api/types.gen.go | 12 ++-- management/server/http/peers_handler.go | 12 ++-- management/server/peer.go | 44 ++++++++++---- management/server/peer/peer.go | 2 +- management/server/route.go | 76 +++++++++++++++++++++++++ 10 files changed, 143 insertions(+), 44 deletions(-) diff --git a/management/server/account.go b/management/server/account.go index 46cf04e959f..2d10c137b07 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -309,9 +309,9 @@ func (a *Account) filterRoutesByIPv6Enabled(routes []*route.Route, v6Supported b return routes } var filteredRoutes []*route.Route - for _, route := range routes { - if route.Network.Addr().Is4() { - filteredRoutes = append(filteredRoutes, route) + for _, rt := range routes { + if rt.Network.Addr().Is4() { + filteredRoutes = append(filteredRoutes, rt) } } return filteredRoutes @@ -350,6 +350,10 @@ func (a *Account) getRoutingPeerRoutes(peerID string) (enabledRoutes []*route.Ro } for _, r := range a.Routes { + // Skip IPv6 routes if IPv6 is currently not enabled. + if peer.IP6 == nil && r.NetworkType == route.IPv6Network { + continue + } for _, groupID := range r.PeerGroups { group := a.GetGroup(groupID) if group == nil { diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index a2becd4cee8..20d8b22298c 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -146,6 +146,7 @@ const ( PeerIPv6InheritEnabled // PeerIPv6InheritDisabled indicates that IPv6 was disabled for a peer due to a change in group memberships. PeerIPv6InheritDisabled + RouteDisabledByDisablingV6 ) var activityMap = map[Activity]Code{ @@ -212,10 +213,11 @@ var activityMap = map[Activity]Code{ PostureCheckCreated: {"Posture check created", "posture.check.created"}, PostureCheckUpdated: {"Posture check updated", "posture.check.updated"}, PostureCheckDeleted: {"Posture check deleted", "posture.check.deleted"}, - PeerIPv6Enabled: {"Peer IPv6 enabled by user", "peer.login.ipv6.manual_enable"}, - PeerIPv6Disabled: {"Peer IPv6 disabled by user", "peer.login.ipv6.manual_disable"}, - PeerIPv6InheritDisabled: {"Peer IPv6 disabled due to change in group settings or membership", "peer.login.ipv6.inherit_disable"}, - PeerIPv6InheritEnabled: {"Peer IPv6 enabled due to change in group settings or membership", "peer.login.ipv6.inherit_enable"}, + PeerIPv6Enabled: {"Peer IPv6 enabled by user", "peer.ipv6.manual_enable"}, + PeerIPv6Disabled: {"Peer IPv6 disabled by user", "peer.ipv6.manual_disable"}, + PeerIPv6InheritDisabled: {"Peer IPv6 disabled due to change in group settings or membership", "peer.ipv6.inherit_disable"}, + PeerIPv6InheritEnabled: {"Peer IPv6 enabled due to change in group settings or membership", "peer.ipv6.inherit_enable"}, + RouteDisabledByDisablingV6: {"IPv6 Route was disabled because IPv6 was disabled for the routing peer", "route.disable.ipv6_disabled"}, } // StringCode returns a string code of the activity diff --git a/management/server/file_store.go b/management/server/file_store.go index 0bc84ab4b0b..ad514781fbc 100644 --- a/management/server/file_store.go +++ b/management/server/file_store.go @@ -176,13 +176,6 @@ func restore(file string) (*FileStore, error) { } } - // for migration - for _, peer := range account.Peers { - if peer.V6Setting == "" { - peer.V6Setting = "inherit" - } - } - allGroup, err := account.GetGroupAll() if err != nil { log.Errorf("unable to find the All group, this should happen only when migrate from a version that didn't support groups. Error: %v", err) diff --git a/management/server/group.go b/management/server/group.go index df374178b16..d9ea1404ef4 100644 --- a/management/server/group.go +++ b/management/server/group.go @@ -149,7 +149,7 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G for _, peer := range peersToUpdate { peerObj := account.GetPeer(peer) - update, err := am.DeterminePeerV6(userID, account, peerObj) + update, err := am.DeterminePeerV6(account, peerObj) if err != nil { return err } diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index ea85bc471c4..970f13a6610 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -236,8 +236,8 @@ components: example: true ipv6_enabled: type: string - enum: [enabled, disabled, inherit] - example: false + enum: [enabled, disabled, auto] + example: auto required: - name - ssh_enabled @@ -313,8 +313,8 @@ components: ipv6_enabled: description: Whether IPv6 is enabled for this peer. type: string - enum: [enabled, disabled, inherit] - example: inherit + enum: [enabled, disabled, auto] + example: auto dns_label: description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud type: string @@ -628,7 +628,7 @@ components: type: string example: "ch8i4ug6lnn4g9hqv7m1" ipv6_enabled: - description: Whether IPv6 should be enabled for all members with IPv6 set to "inherit" + description: Whether IPv6 should be enabled for all members with IPv6 set to "auto" type: boolean required: - name @@ -644,7 +644,7 @@ components: items: $ref: '#/components/schemas/PeerMinimum' ipv6_enabled: - description: Whether IPv6 should be enabled for all members with IPv6 set to "inherit" + description: Whether IPv6 should be enabled for all members with IPv6 set to "auto" type: boolean required: - peers diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index be4169e9bd9..d7db20a3ec0 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -76,30 +76,30 @@ const ( // Defines values for PeerIpv6Enabled. const ( + PeerIpv6EnabledAuto PeerIpv6Enabled = "auto" PeerIpv6EnabledDisabled PeerIpv6Enabled = "disabled" PeerIpv6EnabledEnabled PeerIpv6Enabled = "enabled" - PeerIpv6EnabledInherit PeerIpv6Enabled = "inherit" ) // Defines values for PeerBaseIpv6Enabled. const ( + PeerBaseIpv6EnabledAuto PeerBaseIpv6Enabled = "auto" PeerBaseIpv6EnabledDisabled PeerBaseIpv6Enabled = "disabled" PeerBaseIpv6EnabledEnabled PeerBaseIpv6Enabled = "enabled" - PeerBaseIpv6EnabledInherit PeerBaseIpv6Enabled = "inherit" ) // Defines values for PeerBatchIpv6Enabled. const ( + PeerBatchIpv6EnabledAuto PeerBatchIpv6Enabled = "auto" PeerBatchIpv6EnabledDisabled PeerBatchIpv6Enabled = "disabled" PeerBatchIpv6EnabledEnabled PeerBatchIpv6Enabled = "enabled" - PeerBatchIpv6EnabledInherit PeerBatchIpv6Enabled = "inherit" ) // Defines values for PeerRequestIpv6Enabled. const ( + PeerRequestIpv6EnabledAuto PeerRequestIpv6Enabled = "auto" PeerRequestIpv6EnabledDisabled PeerRequestIpv6Enabled = "disabled" PeerRequestIpv6EnabledEnabled PeerRequestIpv6Enabled = "enabled" - PeerRequestIpv6EnabledInherit PeerRequestIpv6Enabled = "inherit" ) // Defines values for PolicyRuleAction. @@ -311,7 +311,7 @@ type Group struct { // Id Group ID Id string `json:"id"` - // Ipv6Enabled Whether IPv6 should be enabled for all members with IPv6 set to "inherit" + // Ipv6Enabled Whether IPv6 should be enabled for all members with IPv6 set to "auto" Ipv6Enabled bool `json:"ipv6_enabled"` // Issued How group was issued by API or from JWT token @@ -344,7 +344,7 @@ type GroupMinimum struct { // GroupRequest defines model for GroupRequest. type GroupRequest struct { - // Ipv6Enabled Whether IPv6 should be enabled for all members with IPv6 set to "inherit" + // Ipv6Enabled Whether IPv6 should be enabled for all members with IPv6 set to "auto" Ipv6Enabled bool `json:"ipv6_enabled"` // Name Group name identifier diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go index 7c667555cc9..ede6317841e 100644 --- a/management/server/http/peers_handler.go +++ b/management/server/http/peers_handler.go @@ -75,8 +75,8 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe return } - v6Status := nbpeer.V6Inherit - if req.Ipv6Enabled != api.PeerRequestIpv6EnabledInherit { + v6Status := nbpeer.V6Auto + if req.Ipv6Enabled != api.PeerRequestIpv6EnabledAuto { v6Status = nbpeer.V6Status(req.Ipv6Enabled) } @@ -246,8 +246,8 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD ip6string := peer.IP6.String() ip6 = &ip6string } - v6Status := api.PeerIpv6EnabledInherit - if peer.V6Setting != nbpeer.V6Inherit { + v6Status := api.PeerIpv6EnabledAuto + if peer.V6Setting != nbpeer.V6Auto { v6Status = api.PeerIpv6Enabled(peer.V6Setting) } @@ -291,8 +291,8 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn ip6string := peer.IP6.String() ip6 = &ip6string } - v6Status := api.PeerBatchIpv6EnabledInherit - if peer.V6Setting != nbpeer.V6Inherit { + v6Status := api.PeerBatchIpv6EnabledAuto + if peer.V6Setting != nbpeer.V6Auto { v6Status = api.PeerBatchIpv6Enabled(peer.V6Setting) } diff --git a/management/server/peer.go b/management/server/peer.go index 93a9c12208b..14b3b0d08a9 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -14,6 +14,7 @@ import ( "github.com/netbirdio/netbird/management/server/activity" nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" + nbroute "github.com/netbirdio/netbird/route" log "github.com/sirupsen/logrus" @@ -153,18 +154,27 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected // Note that this change does not get persisted here. // // Returns a boolean that indicates whether the peer changed and needs to be updated in the data source. -func (am *DefaultAccountManager) DeterminePeerV6(userID string, account *Account, peer *nbpeer.Peer) (bool, error) { +func (am *DefaultAccountManager) DeterminePeerV6(account *Account, peer *nbpeer.Peer) (bool, error) { v6Setting := peer.V6Setting - if peer.V6Setting == nbpeer.V6Inherit { + if peer.V6Setting == nbpeer.V6Auto { if peer.Meta.Ipv6Supported { for _, group := range account.Groups { if group.IPv6Enabled && slices.Contains(group.Peers, peer.ID) { v6Setting = nbpeer.V6Enabled + break + } + } + if v6Setting == nbpeer.V6Auto { + for _, route := range account.Routes { + if route.Peer == peer.ID && route.NetworkType == nbroute.IPv6Network { + v6Setting = nbpeer.V6Enabled + break + } } } } - if v6Setting == nbpeer.V6Inherit { + if v6Setting == nbpeer.V6Auto { v6Setting = nbpeer.V6Disabled } } @@ -212,7 +222,7 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nb if peer.V6Setting != update.V6Setting { peer.V6Setting = update.V6Setting prevV6 := peer.IP6 - v6StatusChanged, err := am.DeterminePeerV6(userID, account, peer) + v6StatusChanged, err := am.DeterminePeerV6(account, peer) if err != nil { return nil, err } @@ -220,6 +230,14 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nb am.StoreEvent(userID, peer.IP6.String(), account.Id, activity.PeerIPv6Enabled, peer.EventMeta(am.GetDNSDomain())) } else if v6StatusChanged && peer.IP6 == nil { am.StoreEvent(userID, prevV6.String(), account.Id, activity.PeerIPv6Disabled, peer.EventMeta(am.GetDNSDomain())) + + for _, route := range account.Routes { + if route.Peer == peer.ID && route.NetworkType == nbroute.IPv6Network { + route.Enabled = false + account.Routes[route.ID] = route + am.StoreEvent(userID, prevV6.String(), account.Id, activity.RouteDisabledByDisablingV6, peer.EventMeta(am.GetDNSDomain())) + } + } } } @@ -482,7 +500,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P LastLogin: time.Now().UTC(), LoginExpirationEnabled: addedByUser, Ephemeral: ephemeral, - V6Setting: "", // corresponds to "inherit from groups" + V6Setting: "", // corresponds to "auto" } if account.Settings.Extra != nil { @@ -517,7 +535,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P } } - _, err = am.DeterminePeerV6(userID, account, newPeer) + _, err = am.DeterminePeerV6(account, newPeer) if err != nil { return nil, nil, err } @@ -655,7 +673,7 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw shouldStoreAccount = true } - updated = disableNoLongerSupportedFeatures(peer) + updated = disableNoLongerSupportedFeatures(account, peer) if updated { shouldStoreAccount = true } @@ -678,11 +696,17 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil } -func disableNoLongerSupportedFeatures(peer *nbpeer.Peer) bool { +func disableNoLongerSupportedFeatures(account *Account, peer *nbpeer.Peer) bool { if !peer.Meta.Ipv6Supported && peer.IP6 != nil { peer.IP6 = nil - // Reset V6 setting to default "inherit" so that we maintain consistent state if IPv6 is ever supported again. - peer.V6Setting = nbpeer.V6Inherit + // Reset V6 setting to default "auto" so that we maintain consistent state if IPv6 is ever supported again. + peer.V6Setting = nbpeer.V6Auto + for _, route := range account.Routes { + if route.NetworkType == nbroute.IPv6Network { + route.Enabled = false + account.Routes[route.ID] = route + } + } return true } return false diff --git a/management/server/peer/peer.go b/management/server/peer/peer.go index be2be9727ea..2db91bd9a17 100644 --- a/management/server/peer/peer.go +++ b/management/server/peer/peer.go @@ -54,7 +54,7 @@ type V6Status string const ( // Inherit IPv6 settings from groups (=> if one group the peer is a member of has IPv6 enabled, it will be enabled). - V6Inherit V6Status = "" + V6Auto V6Status = "" // Enable IPv6 regardless of group settings, as long as it is supported. V6Enabled V6Status = "enabled" // Disable IPv6 regardless of group settings. diff --git a/management/server/route.go b/management/server/route.go index 4de552a2d43..cb505d6ad93 100644 --- a/management/server/route.go +++ b/management/server/route.go @@ -1,6 +1,7 @@ package server import ( + nbpeer "github.com/netbirdio/netbird/management/server/peer" "net/netip" "unicode/utf8" @@ -180,6 +181,25 @@ func (am *DefaultAccountManager) CreateRoute(accountID, network, peerID string, account.Routes[newRoute.ID] = &newRoute + // IPv6 route must only be created with IPv6 enabled peers, creating an IPv6 enabled route may enable IPv6 for + // peers with V6Setting = Auto. + if peerID != "" && prefixType == route.IPv6Network && newRoute.Enabled { + peer := account.GetPeer(peerID) + if peer.V6Setting == nbpeer.V6Disabled || !peer.Meta.Ipv6Supported { + return nil, status.Errorf( + status.InvalidArgument, + "IPv6 must be enabled for peer %s to be used in route %s", + peer.Name, newPrefix.String()) + } else if peer.IP6 == nil { + _, err = am.DeterminePeerV6(account, peer) + if err != nil { + return nil, err + } + account.UpdatePeer(peer) + } + + } + account.Network.IncSerial() if err = am.Store.SaveAccount(account); err != nil { return nil, err @@ -239,8 +259,49 @@ func (am *DefaultAccountManager) SaveRoute(accountID, userID string, routeToSave return err } + oldRoute := account.Routes[routeToSave.ID] + account.Routes[routeToSave.ID] = routeToSave + // Check if old peer's IPv6 status needs to be recalculated. + // Must happen if route is an IPv6 route, and either: + // - The routing peer has changed + // - The route has been disabled + // - (the route has been enabled) => caught in the next if-block + if oldRoute.Peer != "" && routeToSave.NetworkType == route.IPv6Network && ((oldRoute.Enabled && !routeToSave.Enabled) || oldRoute.Peer != routeToSave.Peer) { + oldPeer := account.GetPeer(oldRoute.Peer) + if oldPeer.V6Setting == nbpeer.V6Auto { + changed, err := am.DeterminePeerV6(account, oldPeer) + if err != nil { + return err + } + if changed { + account.UpdatePeer(oldPeer) + } + } + } + // Check if new peer's IPv6 status needs to be recalculated. + // Must happen if route is an IPv6 route, and either: + // - The routing peer has changed + // - The route has been enabled + // - (The route has been disabled) => caught in previous if-block + if oldRoute.Peer != "" && routeToSave.NetworkType == route.IPv6Network && routeToSave.Enabled && (!oldRoute.Enabled || oldRoute.Peer != routeToSave.Peer) { + newPeer := account.GetPeer(routeToSave.Peer) + if newPeer.V6Setting == nbpeer.V6Disabled || !newPeer.Meta.Ipv6Supported { + return status.Errorf( + status.InvalidArgument, + "IPv6 must be enabled for peer %s to be used in route %s", + newPeer.Name, routeToSave.Network.String()) + } else if newPeer.IP6 == nil { + _, err = am.DeterminePeerV6(account, newPeer) + if err != nil { + return err + } + account.UpdatePeer(newPeer) + } + + } + account.Network.IncSerial() if err = am.Store.SaveAccount(account); err != nil { return err @@ -269,6 +330,21 @@ func (am *DefaultAccountManager) DeleteRoute(accountID, routeID, userID string) } delete(account.Routes, routeID) + // IPv6 route must only be created with IPv6 enabled peers, creating an IPv6 enabled route may enable IPv6 for + // peers with V6Setting = Auto. + if routy.Peer != "" && routy.Enabled && routy.NetworkType == route.IPv6Network { + oldPeer := account.GetPeer(routy.Peer) + if oldPeer.V6Setting == nbpeer.V6Auto { + changed, err := am.DeterminePeerV6(account, oldPeer) + if err != nil { + return err + } + if changed { + account.UpdatePeer(oldPeer) + } + } + } + account.Network.IncSerial() if err = am.Store.SaveAccount(account); err != nil { return err From 58f6d1412b76fc3290b25c365cdb7fb295e393cd Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Fri, 1 Mar 2024 02:04:49 +0100 Subject: [PATCH 23/42] Fix IPv6 network generation randomness --- management/server/network.go | 38 +++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/management/server/network.go b/management/server/network.go index 56cba34128c..4994e16c613 100644 --- a/management/server/network.go +++ b/management/server/network.go @@ -1,13 +1,13 @@ package server import ( + crand "crypto/rand" + "encoding/binary" + "github.com/c-robinson/iplib" + "github.com/rs/xid" "math/rand" "net" "sync" - "time" - - "github.com/c-robinson/iplib" - "github.com/rs/xid" nbdns "github.com/netbirdio/netbird/dns" nbpeer "github.com/netbirdio/netbird/management/server/peer" @@ -30,6 +30,8 @@ const ( AllowedIP6sFormat = "%s/128" ) +var rng = initializeRng() + type NetworkMap struct { Peers []*nbpeer.Peer Network *Network @@ -51,16 +53,23 @@ type Network struct { mu sync.Mutex `json:"-" gorm:"-"` } +func initializeRng() *rand.Rand { + seed := make([]byte, 8) + _, err := crand.Read(seed) + if err != nil { + return nil + } + s := rand.NewSource(int64(binary.LittleEndian.Uint64(seed))) + return rand.New(s) +} + // NewNetwork creates a new Network initializing it with a Serial=0 // It takes a random /16 subnet from 100.64.0.0/10 (64 different subnets) func NewNetwork(enableV6 bool) *Network { n := iplib.NewNet4(net.ParseIP("100.64.0.0"), NetSize) sub, _ := n.Subnet(SubnetSize) - - s := rand.NewSource(time.Now().Unix()) - r := rand.New(s) - intn := r.Intn(len(sub)) + intn := rng.Intn(len(sub)) var n6 *net.IPNet = nil if enableV6 { @@ -76,14 +85,12 @@ func NewNetwork(enableV6 bool) *Network { } func GenerateNetwork6() *net.IPNet { - s := rand.NewSource(time.Now().Unix()) - r := rand.New(s) addrbuf := make([]byte, 16) addrbuf[0] = 0xfd addrbuf[1] = 0x00 addrbuf[2] = 0xb1 addrbuf[3] = 0x4d - _, _ = r.Read(addrbuf[4:Subnet6Size]) + _, _ = rng.Read(addrbuf[4:Subnet6Size]) n6 := iplib.NewNet6(addrbuf, Subnet6Size*8, 0).IPNet return &n6 @@ -130,9 +137,7 @@ func AllocatePeerIP(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) { } // pick a random IP - s := rand.NewSource(time.Now().Unix()) - r := rand.New(s) - intn := r.Intn(len(ips)) + intn := rng.Intn(len(ips)) return ips[intn], nil } @@ -149,16 +154,13 @@ func AllocatePeerIP6(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) { maskSize, _ := ipNet.Mask.Size() - s := rand.NewSource(time.Now().Unix()) - r := rand.New(s) - // TODO for small subnet sizes, randomly generating values until we don't get a duplicate is inefficient and could // lead to many loop iterations, using a method similar to IPv4 would be preferable here. addrbuf := make(net.IP, 16) copy(addrbuf, ipNet.IP.To16()) for duplicate := true; duplicate; _, duplicate = takenIPMap[addrbuf.String()] { - _, _ = r.Read(addrbuf[(maskSize / 8):16]) + _, _ = rng.Read(addrbuf[(maskSize / 8):16]) } return addrbuf, nil } From 52dd3d57987f86815b935151fd73a385b72a8d3d Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Sat, 2 Mar 2024 02:13:31 +0100 Subject: [PATCH 24/42] Fix a bunch of compilation issues and test failures --- client/internal/dns/server_test.go | 16 ++++++++++------ client/internal/routemanager/mock.go | 4 ++++ .../internal/routemanager/systemops_android.go | 2 +- client/internal/routemanager/systemops_ios.go | 2 +- .../routemanager/systemops_nonandroid_test.go | 5 +++-- .../internal/routemanager/systemops_nonlinux.go | 2 +- management/server/account_test.go | 4 ++-- management/server/http/groups_handler.go | 2 +- management/server/http/peers_handler_test.go | 6 +++--- management/server/mock_server/account_mock.go | 6 +++--- management/server/network.go | 12 ++++++++++++ 11 files changed, 41 insertions(+), 20 deletions(-) diff --git a/client/internal/dns/server_test.go b/client/internal/dns/server_test.go index 4041aac43c5..36330e55471 100644 --- a/client/internal/dns/server_test.go +++ b/client/internal/dns/server_test.go @@ -345,7 +345,7 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) { } privKey, _ := wgtypes.GeneratePrivateKey() - wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.1/32", "fd00:1234:dead:beef::1/128", 33100, privKey.String(), iface.DefaultMTU, newNet, nil) + wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.1/32", "", 33100, privKey.String(), iface.DefaultMTU, newNet, nil) if err != nil { t.Errorf("build interface wireguard: %v", err) return @@ -600,7 +600,7 @@ func TestDNSServerUpstreamDeactivateCallback(t *testing.T) { } func TestDNSPermanent_updateHostDNS_emptyUpstream(t *testing.T) { - wgIFace, err := createWgInterfaceWithBind(t) + wgIFace, err := createWgInterfaceWithBind(t, false) if err != nil { t.Fatal("failed to initialize wg interface") } @@ -626,7 +626,7 @@ func TestDNSPermanent_updateHostDNS_emptyUpstream(t *testing.T) { } func TestDNSPermanent_updateUpstream(t *testing.T) { - wgIFace, err := createWgInterfaceWithBind(t) + wgIFace, err := createWgInterfaceWithBind(t, false) if err != nil { t.Fatal("failed to initialize wg interface") } @@ -718,7 +718,7 @@ func TestDNSPermanent_updateUpstream(t *testing.T) { } func TestDNSPermanent_matchOnly(t *testing.T) { - wgIFace, err := createWgInterfaceWithBind(t) + wgIFace, err := createWgInterfaceWithBind(t, false) if err != nil { t.Fatal("failed to initialize wg interface") } @@ -784,7 +784,7 @@ func TestDNSPermanent_matchOnly(t *testing.T) { } } -func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) { +func createWgInterfaceWithBind(t *testing.T, enableV6 bool) (*iface.WGIface, error) { t.Helper() ov := os.Getenv("NB_WG_KERNEL_DISABLED") defer t.Setenv("NB_WG_KERNEL_DISABLED", ov) @@ -797,7 +797,11 @@ func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) { } privKey, _ := wgtypes.GeneratePrivateKey() - wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.2/24", "fd00:1234:dead:beef::1/128", 33100, privKey.String(), iface.DefaultMTU, newNet, nil) + v6Addr := "" + if enableV6 { + v6Addr = "fd00:1234:dead:beef::1/128" + } + wgIface, err := iface.NewWGIFace("utun2301", "100.66.100.2/24", v6Addr, 33100, privKey.String(), iface.DefaultMTU, newNet, nil) if err != nil { t.Fatalf("build interface wireguard: %v", err) return nil, err diff --git a/client/internal/routemanager/mock.go b/client/internal/routemanager/mock.go index a1214cbb9ec..61fd9eba5ac 100644 --- a/client/internal/routemanager/mock.go +++ b/client/internal/routemanager/mock.go @@ -42,6 +42,10 @@ func (m *MockManager) EnableServerRouter(firewall firewall.Manager) error { panic("implement me") } +func (m *MockManager) ResetV6Routes() { + panic("implement me") +} + // Stop mock implementation of Stop from Manager interface func (m *MockManager) Stop() { if m.StopFunc != nil { diff --git a/client/internal/routemanager/systemops_android.go b/client/internal/routemanager/systemops_android.go index 08675a16aa6..32dcd327649 100644 --- a/client/internal/routemanager/systemops_android.go +++ b/client/internal/routemanager/systemops_android.go @@ -8,6 +8,6 @@ func addToRouteTableIfNoExists(prefix netip.Prefix, addr string, devName string) return nil } -func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string) error { +func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string, devName string) error { return nil } diff --git a/client/internal/routemanager/systemops_ios.go b/client/internal/routemanager/systemops_ios.go index adfdc7e0816..f60223f5a1d 100644 --- a/client/internal/routemanager/systemops_ios.go +++ b/client/internal/routemanager/systemops_ios.go @@ -10,6 +10,6 @@ func addToRouteTableIfNoExists(prefix netip.Prefix, addr string, devName string) return nil } -func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string) error { +func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string, devName string) error { return nil } diff --git a/client/internal/routemanager/systemops_nonandroid_test.go b/client/internal/routemanager/systemops_nonandroid_test.go index e153c414e46..10176eb4136 100644 --- a/client/internal/routemanager/systemops_nonandroid_test.go +++ b/client/internal/routemanager/systemops_nonandroid_test.go @@ -67,7 +67,7 @@ func TestAddRemoveRoutes(t *testing.T) { exists, err := existsInRouteTable(testCase.prefix) require.NoError(t, err, "existsInRouteTable should not return err") if exists && testCase.shouldRouteToWireguard { - err = removeFromRouteTableIfNonSystem(testCase.prefix, wgInterface.Address().IP.String()) + err = removeFromRouteTableIfNonSystem(testCase.prefix, wgInterface.Address().IP.String(), wgInterface.Name()) require.NoError(t, err, "removeFromRouteTableIfNonSystem should not return err") prefixGateway, err = getExistingRIBRouteGateway(testCase.prefix) @@ -190,6 +190,7 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) { require.NoError(t, err, "should create testing wireguard interface") MockAddr := wgInterface.Address().IP.String() + MockDevName := wgInterface.Name() // Prepare the environment if testCase.preExistingPrefix.IsValid() { @@ -208,7 +209,7 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) { require.True(t, ok, "route should exist") // remove route again if added - err = removeFromRouteTableIfNonSystem(testCase.prefix, MockAddr) + err = removeFromRouteTableIfNonSystem(testCase.prefix, MockAddr, MockDevName) require.NoError(t, err, "should not return err") } diff --git a/client/internal/routemanager/systemops_nonlinux.go b/client/internal/routemanager/systemops_nonlinux.go index 6bc501ada71..91965e656b0 100644 --- a/client/internal/routemanager/systemops_nonlinux.go +++ b/client/internal/routemanager/systemops_nonlinux.go @@ -23,7 +23,7 @@ func addToRouteTable(prefix netip.Prefix, addr string, devName string) error { return nil } -func removeFromRouteTable(prefix netip.Prefix, addr string) error { +func removeFromRouteTable(prefix netip.Prefix, addr string, devName string) error { args := []string{"delete", prefix.String()} if runtime.GOOS == "darwin" { args = append(args, addr) diff --git a/management/server/account_test.go b/management/server/account_test.go index b12e2fa0213..87c0c68e576 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -1643,11 +1643,11 @@ func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) { // disable expiration first update := peer.Copy() update.LoginExpirationEnabled = false - _, err = manager.UpdatePeer(account.Id, userID, update, false) + _, err = manager.UpdatePeer(account.Id, userID, update) require.NoError(t, err, "unable to update peer") // enabling expiration should trigger the routine update.LoginExpirationEnabled = true - _, err = manager.UpdatePeer(account.Id, userID, update, false) + _, err = manager.UpdatePeer(account.Id, userID, update) require.NoError(t, err, "unable to update peer") failed := waitTimeout(wg, time.Second) diff --git a/management/server/http/groups_handler.go b/management/server/http/groups_handler.go index e2530fb176e..9154d7f1f2c 100644 --- a/management/server/http/groups_handler.go +++ b/management/server/http/groups_handler.go @@ -105,7 +105,7 @@ func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) { } if allGroup.ID == groupID { - if len(peers) != len(allGroup.Peers) { + if len(peers) != len(allGroup.Peers) || req.Name != allGroup.Name { util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w) return } diff --git a/management/server/http/peers_handler_test.go b/management/server/http/peers_handler_test.go index 9dc246dd194..4272e22e542 100644 --- a/management/server/http/peers_handler_test.go +++ b/management/server/http/peers_handler_test.go @@ -29,7 +29,7 @@ const noUpdateChannelTestPeerID = "no-update-channel" func initTestMetaData(peers ...*nbpeer.Peer) *PeersHandler { return &PeersHandler{ accountManager: &mock_server.MockAccountManager{ - UpdatePeerFunc: func(accountID, userID string, update *nbpeer.Peer, enableV6 bool) (*nbpeer.Peer, error) { + UpdatePeerFunc: func(accountID, userID string, update *nbpeer.Peer) (*nbpeer.Peer, error) { var p *nbpeer.Peer for _, peer := range peers { if update.ID == peer.ID { @@ -37,10 +37,10 @@ func initTestMetaData(peers ...*nbpeer.Peer) *PeersHandler { break } } - if enableV6 && p.IP6 == nil { + if p.V6Setting == nbpeer.V6Enabled && p.IP6 == nil { ip6 := net.ParseIP("2001:db8::dead:beef") p.IP6 = &ip6 - } else if !enableV6 { + } else { p.IP6 = nil } p.SSHEnabled = update.SSHEnabled diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index 4c165205b24..7d4161d3b0e 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -51,7 +51,7 @@ type MockAccountManager struct { MarkPATUsedFunc func(pat string) error UpdatePeerMetaFunc func(peerID string, meta nbpeer.PeerSystemMeta) error UpdatePeerSSHKeyFunc func(peerID string, sshKey string) error - UpdatePeerFunc func(accountID, userID string, peer *nbpeer.Peer, enableV6 bool) (*nbpeer.Peer, error) + UpdatePeerFunc func(accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error) CreateRouteFunc func(accountID, prefix, peer string, peerGroups []string, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) GetRouteFunc func(accountID, routeID, userID string) (*route.Route, error) SaveRouteFunc func(accountID, userID string, route *route.Route) error @@ -398,9 +398,9 @@ func (am *MockAccountManager) UpdatePeerSSHKey(peerID string, sshKey string) err } // UpdatePeer mocks UpdatePeerFunc function of the account manager -func (am *MockAccountManager) UpdatePeer(accountID, userID string, peer *nbpeer.Peer, enableV6 bool) (*nbpeer.Peer, error) { +func (am *MockAccountManager) UpdatePeer(accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error) { if am.UpdatePeerFunc != nil { - return am.UpdatePeerFunc(accountID, userID, peer, enableV6) + return am.UpdatePeerFunc(accountID, userID, peer) } return nil, status.Errorf(codes.Unimplemented, "method UpdatePeer is not implemented") } diff --git a/management/server/network.go b/management/server/network.go index 4994e16c613..cf5a5952632 100644 --- a/management/server/network.go +++ b/management/server/network.go @@ -30,7 +30,10 @@ const ( AllowedIP6sFormat = "%s/128" ) +// Global random number generator for IP addresses +// Accesses to the RNG must always be protected using rngLock (RNG sources are not thread-safe) var rng = initializeRng() +var rngLock = sync.Mutex{} type NetworkMap struct { Peers []*nbpeer.Peer @@ -69,7 +72,9 @@ func NewNetwork(enableV6 bool) *Network { n := iplib.NewNet4(net.ParseIP("100.64.0.0"), NetSize) sub, _ := n.Subnet(SubnetSize) + rngLock.Lock() intn := rng.Intn(len(sub)) + rngLock.Unlock() var n6 *net.IPNet = nil if enableV6 { @@ -90,7 +95,10 @@ func GenerateNetwork6() *net.IPNet { addrbuf[1] = 0x00 addrbuf[2] = 0xb1 addrbuf[3] = 0x4d + + rngLock.Lock() _, _ = rng.Read(addrbuf[4:Subnet6Size]) + rngLock.Unlock() n6 := iplib.NewNet6(addrbuf, Subnet6Size*8, 0).IPNet return &n6 @@ -137,7 +145,9 @@ func AllocatePeerIP(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) { } // pick a random IP + rngLock.Lock() intn := rng.Intn(len(ips)) + rngLock.Unlock() return ips[intn], nil } @@ -160,7 +170,9 @@ func AllocatePeerIP6(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) { addrbuf := make(net.IP, 16) copy(addrbuf, ipNet.IP.To16()) for duplicate := true; duplicate; _, duplicate = takenIPMap[addrbuf.String()] { + rngLock.Lock() _, _ = rng.Read(addrbuf[(maskSize / 8):16]) + rngLock.Unlock() } return addrbuf, nil } From a02e5511639858e8eb013e3212b8efbdb10202b0 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Wed, 6 Mar 2024 20:25:38 +0100 Subject: [PATCH 25/42] Replace method calls that are unavailable in Go 1.21 --- management/server/group.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/management/server/group.go b/management/server/group.go index d9ea1404ef4..9c3a097d658 100644 --- a/management/server/group.go +++ b/management/server/group.go @@ -2,8 +2,6 @@ package server import ( "fmt" - "slices" - log "github.com/sirupsen/logrus" "github.com/netbirdio/netbird/management/server/activity" @@ -142,9 +140,9 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G // Need to check whether IPv6 status has changed for all potentially affected peers. peersToUpdate := removedPeers if exists && oldGroup.IPv6Enabled != newGroup.IPv6Enabled { - peersToUpdate = slices.Concat(peersToUpdate, newGroup.Peers) + peersToUpdate = append(peersToUpdate, newGroup.Peers...) } else { - peersToUpdate = slices.Concat(peersToUpdate, addedPeers) + peersToUpdate = append(peersToUpdate, addedPeers...) } for _, peer := range peersToUpdate { From f1dd4550330483dfd3f3fb6d3dae26e1d4f6cd46 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Wed, 6 Mar 2024 21:30:30 +0100 Subject: [PATCH 26/42] Fix nil dereference in cleanUpDefaultForwardRules6 --- client/firewall/nftables/route_linux.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/firewall/nftables/route_linux.go b/client/firewall/nftables/route_linux.go index 76325b0e312..ebbb7d137e3 100644 --- a/client/firewall/nftables/route_linux.go +++ b/client/firewall/nftables/route_linux.go @@ -520,8 +520,8 @@ func (r *router) cleanUpDefaultForwardRules() error { return r.conn.Flush() } func (r *router) cleanUpDefaultForwardRules6() error { - if r.filterTable == nil { - r.isDefaultFwdRulesEnabled = false + if r.filterTable6 == nil { + r.isDefaultFwdRulesEnabled6 = false return nil } From a5361f3a446670c10462ce400943948b753a4fa3 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Wed, 6 Mar 2024 22:52:49 +0100 Subject: [PATCH 27/42] Fix nil pointer dereference when persisting IPv6 network in sqlite --- management/server/network.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/server/network.go b/management/server/network.go index cf5a5952632..e4370b5a72c 100644 --- a/management/server/network.go +++ b/management/server/network.go @@ -47,7 +47,7 @@ type NetworkMap struct { type Network struct { Identifier string `json:"id"` Net net.IPNet `gorm:"serializer:gob"` - Net6 *net.IPNet `gorm:"serializer:gob"` + Net6 *net.IPNet `gorm:"serializer:json"` // Can't use gob serializer, as it cannot encode nil values. Dns string // Serial is an ID that increments by 1 when any change to the network happened (e.g. new peer has been added). // Used to synchronize state to the client apps. From ba2593084fa6229c589f13ac3bfefd1c70597e61 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Thu, 7 Mar 2024 02:28:51 +0100 Subject: [PATCH 28/42] Clean up of client-side code changes for IPv6 --- client/firewall/create.go | 2 + client/firewall/create_linux.go | 4 + client/firewall/nftables/acl_linux.go | 438 ++++++++++-------- client/firewall/nftables/manager_linux.go | 44 +- client/firewall/nftables/route_linux.go | 184 +++----- client/internal/acl/manager.go | 3 +- client/internal/routemanager/manager.go | 4 - client/internal/routemanager/server.go | 1 - .../routemanager/server_nonandroid.go | 8 +- .../internal/routemanager/systemops_linux.go | 21 +- 10 files changed, 347 insertions(+), 362 deletions(-) diff --git a/client/firewall/create.go b/client/firewall/create.go index 1ae5b4310d7..5844656e887 100644 --- a/client/firewall/create.go +++ b/client/firewall/create.go @@ -31,6 +31,8 @@ func NewFirewall(context context.Context, iface IFaceMapper) (firewall.Manager, return fm, nil } +// Returns true if the current firewall implementation supports IPv6. +// Currently false for anything non-linux. func SupportsIPv6() bool { return false } diff --git a/client/firewall/create_linux.go b/client/firewall/create_linux.go index f876d0ff8ae..22128f5a4d7 100644 --- a/client/firewall/create_linux.go +++ b/client/firewall/create_linux.go @@ -70,6 +70,8 @@ func NewFirewall(context context.Context, iface IFaceMapper) (firewall.Manager, return nil, errUsp } + // Note for devs: When adding IPv6 support to userspace bind, the implementation of AllowNetbird() has to be + // adjusted accordingly. if err := fm.AllowNetbird(); err != nil { log.Errorf("failed to allow netbird interface traffic: %v", err) } @@ -83,6 +85,8 @@ func NewFirewall(context context.Context, iface IFaceMapper) (firewall.Manager, return fm, nil } +// Returns true if the current firewall implementation supports IPv6. +// Currently true if the firewall is nftables. func SupportsIPv6() bool { return check() == NFTABLES } diff --git a/client/firewall/nftables/acl_linux.go b/client/firewall/nftables/acl_linux.go index f58c19bb07d..68bc28094f9 100644 --- a/client/firewall/nftables/acl_linux.go +++ b/client/firewall/nftables/acl_linux.go @@ -96,13 +96,13 @@ func newAclManager(table *nftables.Table, table6 *nftables.Table, wgIface iFaceM rules: make(map[string]*Rule), } - err = m.createDefaultChains() + err = m.createDefaultChains(false) if err != nil { return nil, err } if m.v6Active { - err = m.createDefaultChains6() + err = m.createDefaultChains(true) if err != nil { return nil, err } @@ -111,6 +111,8 @@ func newAclManager(table *nftables.Table, table6 *nftables.Table, wgIface iFaceM return m, nil } +// PrepareV6Reset prepares the ACL manager for a full V6 table reset (necessary if IPv6 address changes). +// Deletes all IPv6 rules and sets, and sets the V6 active status to false. func (m *AclManager) PrepareV6Reset() (*nftables.Table, error) { if m.workTable6 != nil { for k, r := range m.rules { @@ -130,20 +132,26 @@ func (m *AclManager) PrepareV6Reset() (*nftables.Table, error) { } m.ipsetStore6 = newIpsetStore() } - m.v6Active = m.wgIface.Address6() != nil + // Set to false just in case of concurrent accesses (will be set to actual values in ReinitAfterV6Reset()). + m.v6Active = false + // return the current work table. return m.workTable6, nil } +// ReinitAfterV6Reset reinitializes the IPv6 table after an IPv6 address change. func (m *AclManager) ReinitAfterV6Reset(workTable6 *nftables.Table) error { + // If we have an IPv6 address after the address update, initialize firewall table, else not. if m.wgIface.Address6() != nil { m.workTable6 = workTable6 - err := m.createDefaultChains6() + err := m.createDefaultChains(true) if err != nil { return err } + m.v6Active = true } else { m.workTable6 = nil + m.v6Active = false } return nil } @@ -200,6 +208,11 @@ func (m *AclManager) DeleteRule(rule firewall.Rule) error { return fmt.Errorf("invalid rule type") } + ipsetStorage := m.ipsetStore + if r.ip.To4() == nil { + ipsetStorage = m.ipsetStore6 + } + if r.nftSet == nil { err := m.rConn.DelRule(r.nftRule) if err != nil { @@ -209,7 +222,7 @@ func (m *AclManager) DeleteRule(rule firewall.Rule) error { return m.rConn.Flush() } - ips, ok := m.ipsetStore.ips(r.nftSet.Name) + ips, ok := ipsetStorage.ips(r.nftSet.Name) if !ok { err := m.rConn.DelRule(r.nftRule) if err != nil { @@ -231,7 +244,7 @@ func (m *AclManager) DeleteRule(rule firewall.Rule) error { log.Debugf("flush error of set delete element, %s", r.nftSet.Name) return err } - m.ipsetStore.DeleteIpFromSet(r.nftSet.Name, r.ip) + ipsetStorage.DeleteIpFromSet(r.nftSet.Name, r.ip) } // if after delete, set still contains other IPs, @@ -250,9 +263,9 @@ func (m *AclManager) DeleteRule(rule firewall.Rule) error { } delete(m.rules, r.GetRuleID()) - m.ipsetStore.DeleteReferenceFromIpSet(r.nftSet.Name) + ipsetStorage.DeleteReferenceFromIpSet(r.nftSet.Name) - if m.ipsetStore.HasReferenceToSet(r.nftSet.Name) { + if ipsetStorage.HasReferenceToSet(r.nftSet.Name) { return nil } @@ -260,7 +273,7 @@ func (m *AclManager) DeleteRule(rule firewall.Rule) error { // set itself and associated firewall rule too m.rConn.FlushSet(r.nftSet) m.rConn.DelSet(r.nftSet) - m.ipsetStore.deleteIpset(r.nftSet.Name) + ipsetStorage.deleteIpset(r.nftSet.Name) return nil } @@ -360,7 +373,7 @@ func (m *AclManager) Flush() error { } if err := m.refreshRuleHandles(m.workTable6, m.chainInputRules6); err != nil { - log.Errorf("failed to refresh rule handles ipv6 input chain: %v", err) + log.Errorf("failed to refresh rule handles IPv6 input chain: %v", err) } if err := m.refreshRuleHandles(m.workTable6, m.chainOutputRules6); err != nil { @@ -422,24 +435,35 @@ func (m *AclManager) addIOFiltering(ip net.IP, proto firewall.Protocol, sPort *f }) } + workTable := m.workTable + // Raw bytes of IP to match (if IPv4). rawIP := ip.To4() - table := m.workTable + // source address position (in IPv4) + srcAddrOffset := uint32(12) + // destination address position (in IPv4) + dstAddrOffset := uint32(16) + // address length + addrLen := uint32(4) + // IP set storage for IPv4. + ipsetStorage := m.ipsetStore + + // If rawIP == nil, we have an IPv6 address, replace previously defined values with IPv6 counterparts. if rawIP == nil { rawIP = ip.To16() - table = m.workTable6 + srcAddrOffset = uint32(8) + dstAddrOffset = uint32(24) + addrLen = 16 + workTable = m.workTable6 + ipsetStorage = m.ipsetStore6 } + // check if rawIP contains zeroed IP address value // in that case not add IP match expression into the rule definition if !bytes.HasPrefix(anyIP, rawIP) { - addrLen := uint32(len(rawIP)) - // source address position - addrOffset := uint32(12) - if addrLen == 16 { - addrOffset = uint32(8) - } + addrOffset := srcAddrOffset if direction == firewall.RuleDirectionOUT { - addrOffset += addrLen + addrOffset = dstAddrOffset } expressions = append(expressions, @@ -518,7 +542,7 @@ func (m *AclManager) addIOFiltering(ip net.IP, proto firewall.Protocol, sPort *f chain = m.chainOutputRules } nftRule := m.rConn.InsertRule(&nftables.Rule{ - Table: table, + Table: workTable, Chain: chain, Position: 0, Exprs: expressions, @@ -533,7 +557,7 @@ func (m *AclManager) addIOFiltering(ip net.IP, proto firewall.Protocol, sPort *f } m.rules[ruleId] = rule if ipset != nil { - m.ipsetStore.AddReferenceToIpset(ipset.Name) + ipsetStorage.AddReferenceToIpset(ipset.Name) } return rule, nil } @@ -551,6 +575,35 @@ func (m *AclManager) addPreroutingFiltering(ipset *nftables.Set, proto firewall. return nil, fmt.Errorf("unsupported protocol: %s", proto) } + // Raw bytes of IP to match (if IPv4). + rawIP := ip.To4() + // source address position (in IPv4) + srcAddrOffset := uint32(12) + // destination address position (in IPv4) + dstAddrOffset := uint32(16) + // address length + addrLen := uint32(4) + // Raw bytes of the wireguard interface's IPv4 address. + ifaceRawIP := m.wgIface.Address().IP.To4() + // table to insert rule in + workTable := m.workTable + // chain to insert rule in + preroutingChain := m.chainPrerouting + // IP set store to use + ipsetStorage := m.ipsetStore + + // If rawIP == nil, we have an IPv6 address, replace previously defined values with IPv6 counterparts. + if rawIP == nil { + rawIP = ip.To16() + srcAddrOffset = uint32(8) + dstAddrOffset = uint32(24) + addrLen = 16 + ifaceRawIP = m.wgIface.Address6().IP.To16() + workTable = m.workTable6 + preroutingChain = m.chainPrerouting6 + ipsetStorage = m.ipsetStore6 + } + ruleId := generateRuleIdForMangle(ipset, ip, proto, port) if r, ok := m.rules[ruleId]; ok { return &Rule{ @@ -563,19 +616,6 @@ func (m *AclManager) addPreroutingFiltering(ipset *nftables.Set, proto firewall. var ipExpression expr.Any // add individual IP for match if no ipset defined - rawIP := ip.To4() - if rawIP == nil { - rawIP = ip.To16() - } - addrLen := uint32(len(rawIP)) - // source address position - srcAddrOffset := uint32(12) - dstAddrOffset := uint32(16) - if addrLen == 16 { - srcAddrOffset = uint32(8) - dstAddrOffset = uint32(24) - } - if ipset == nil { ipExpression = &expr.Cmp{ Op: expr.CmpOpEq, @@ -590,11 +630,6 @@ func (m *AclManager) addPreroutingFiltering(ipset *nftables.Set, proto firewall. } } - ifaceRawIP := m.wgIface.Address().IP.To4() - if addrLen == 16 { - ifaceRawIP = m.wgIface.Address6().IP.To16() - } - expressions := []expr.Any{ &expr.Payload{ DestRegister: 1, @@ -654,8 +689,8 @@ func (m *AclManager) addPreroutingFiltering(ipset *nftables.Set, proto firewall. ) nftRule := m.rConn.InsertRule(&nftables.Rule{ - Table: m.workTable, - Chain: m.chainPrerouting, + Table: workTable, + Chain: preroutingChain, Position: 0, Exprs: expressions, UserData: []byte(ruleId), @@ -674,110 +709,65 @@ func (m *AclManager) addPreroutingFiltering(ipset *nftables.Set, proto firewall. m.rules[ruleId] = rule if ipset != nil { - m.ipsetStore.AddReferenceToIpset(ipset.Name) + ipsetStorage.AddReferenceToIpset(ipset.Name) } return rule, nil } -func (m *AclManager) createDefaultChains() (err error) { +func (m *AclManager) createDefaultChains(forV6 bool) (err error) { + workTable := m.workTable + if forV6 { + workTable = m.workTable6 + } + // chainNameInputRules - chain := m.createChain(chainNameInputRules, m.workTable) + chain := m.createChain(chainNameInputRules, workTable) err = m.rConn.Flush() if err != nil { log.Debugf("failed to create chain (%s): %s", chain.Name, err) return err } - m.chainInputRules = chain + chainInputRules := chain + if forV6 { + m.chainInputRules6 = chainInputRules + } else { + m.chainInputRules = chainInputRules + } // chainNameOutputRules - chain = m.createChain(chainNameOutputRules, m.workTable) + chain = m.createChain(chainNameOutputRules, workTable) err = m.rConn.Flush() if err != nil { log.Debugf("failed to create chain (%s): %s", chainNameOutputRules, err) return err } - m.chainOutputRules = chain + chainOutputRules := chain + if forV6 { + m.chainOutputRules6 = chainOutputRules + } else { + m.chainOutputRules = chainOutputRules + } // netbird-acl-input-filter // type filter hook input priority filter; policy accept; - chain = m.createFilterChainWithHook(chainNameInputFilter, nftables.ChainHookInput, m.workTable) + chain = m.createFilterChainWithHook(chainNameInputFilter, nftables.ChainHookInput, workTable) //netbird-acl-input-filter iifname "wt0" ip saddr 100.72.0.0/16 ip daddr != 100.72.0.0/16 accept m.addRouteAllowRule(chain, expr.MetaKeyIIFNAME) m.addFwdAllow(chain, expr.MetaKeyIIFNAME) - m.addJumpRule(chain, m.chainInputRules.Name, expr.MetaKeyIIFNAME) // to netbird-acl-input-rules + m.addJumpRule(chain, chainInputRules.Name, expr.MetaKeyIIFNAME) // to netbird-acl-input-rules m.addDropExpressions(chain, expr.MetaKeyIIFNAME) - - // netbird-acl-output-filter - // type filter hook output priority filter; policy accept; - chain = m.createFilterChainWithHook(chainNameOutputFilter, nftables.ChainHookOutput, m.workTable) - m.addRouteAllowRule(chain, expr.MetaKeyOIFNAME) - m.addFwdAllow(chain, expr.MetaKeyOIFNAME) - m.addJumpRule(chain, m.chainOutputRules.Name, expr.MetaKeyOIFNAME) // to netbird-acl-output-rules - m.addDropExpressions(chain, expr.MetaKeyOIFNAME) - err = m.rConn.Flush() - if err != nil { - log.Debugf("failed to create chain (%s): %s", chainNameOutputFilter, err) - return err - } - - // netbird-acl-forward-filter - m.chainFwFilter = m.createFilterChainWithHook(chainNameForwardFilter, nftables.ChainHookForward, m.workTable) - m.addJumpRulesToRtForward(m.workTable, m.chainFwFilter) // to - m.addMarkAccept(m.workTable, m.chainFwFilter) - m.addJumpRuleToInputChain(m.workTable, m.chainFwFilter, m.chainInputRules) // to netbird-acl-input-rules - m.addDropExpressions(m.chainFwFilter, expr.MetaKeyIIFNAME) - err = m.rConn.Flush() - if err != nil { - log.Debugf("failed to create chain (%s): %s", chainNameForwardFilter, err) - return err - } - - // netbird-acl-output-filter - // type filter hook output priority filter; policy accept; - m.chainPrerouting = m.createPreroutingMangle(m.workTable, false) - err = m.rConn.Flush() - if err != nil { - log.Debugf("failed to create chain (%s): %s", m.chainPrerouting.Name, err) - return err - } - return nil -} - -func (m *AclManager) createDefaultChains6() (err error) { - - // chainNameInputRules - chain := m.createChain(chainNameInputRules, m.workTable6) - err = m.rConn.Flush() - if err != nil { - log.Debugf("failed to create chain (%s): %s", chain.Name, err) - return err - } - m.chainInputRules6 = chain - - // chainNameOutputRules - chain = m.createChain(chainNameOutputRules, m.workTable6) err = m.rConn.Flush() if err != nil { - log.Debugf("failed to create chain (%s): %s", chainNameOutputRules, err) + log.Debugf("failed to create chain (%s): %s", chainNameInputFilter, err) return err } - m.chainOutputRules6 = chain - - // netbird-acl-input-filter - // type filter hook input priority filter; policy accept; - chain = m.createFilterChainWithHook(chainNameInputFilter, nftables.ChainHookInput, m.workTable6) - //netbird-acl-input-filter iifname "wt0" ip saddr 100.72.0.0/16 ip daddr != 100.72.0.0/16 accept - m.addRouteAllowRule(chain, expr.MetaKeyIIFNAME) - m.addFwdAllow(chain, expr.MetaKeyIIFNAME) - m.addJumpRule(chain, m.chainInputRules6.Name, expr.MetaKeyIIFNAME) // to netbird-acl-input-rules - m.addDropExpressions(chain, expr.MetaKeyIIFNAME) // netbird-acl-output-filter // type filter hook output priority filter; policy accept; - chain = m.createFilterChainWithHook(chainNameOutputFilter, nftables.ChainHookOutput, m.workTable6) + chain = m.createFilterChainWithHook(chainNameOutputFilter, nftables.ChainHookOutput, workTable) m.addRouteAllowRule(chain, expr.MetaKeyOIFNAME) m.addFwdAllow(chain, expr.MetaKeyOIFNAME) - m.addJumpRule(chain, m.chainOutputRules6.Name, expr.MetaKeyOIFNAME) // to netbird-acl-output-rules + m.addJumpRule(chain, chainOutputRules.Name, expr.MetaKeyOIFNAME) // to netbird-acl-output-rules m.addDropExpressions(chain, expr.MetaKeyOIFNAME) err = m.rConn.Flush() if err != nil { @@ -786,25 +776,36 @@ func (m *AclManager) createDefaultChains6() (err error) { } // netbird-acl-forward-filter - m.chainFwFilter6 = m.createFilterChainWithHook(chainNameForwardFilter, nftables.ChainHookForward, m.workTable6) - m.addJumpRulesToRtForward(m.workTable6, m.chainFwFilter6) // to - m.addMarkAccept(m.workTable6, m.chainFwFilter6) - m.addJumpRuleToInputChain(m.workTable6, m.chainFwFilter6, m.chainInputRules6) // to netbird-acl-input-rules - m.addDropExpressions(m.chainFwFilter6, expr.MetaKeyIIFNAME) + chain = m.createFilterChainWithHook(chainNameForwardFilter, nftables.ChainHookForward, workTable) + m.addJumpRulesToRtForward(workTable, chain) // to + m.addMarkAccept(workTable, chain) + m.addJumpRuleToInputChain(workTable, chain, chainInputRules) // to netbird-acl-input-rules + m.addDropExpressions(chain, expr.MetaKeyIIFNAME) err = m.rConn.Flush() if err != nil { log.Debugf("failed to create chain (%s): %s", chainNameForwardFilter, err) return err } + if forV6 { + m.chainFwFilter6 = chain + } else { + m.chainFwFilter = chain + } // netbird-acl-output-filter // type filter hook output priority filter; policy accept; - m.chainPrerouting6 = m.createPreroutingMangle(m.workTable6, true) + chain = m.createPreroutingMangle(forV6) err = m.rConn.Flush() if err != nil { - log.Debugf("failed to create chain (%s): %s", m.chainPrerouting.Name, err) + log.Debugf("failed to create chain (%s): %s", chain.Name, err) return err } + if forV6 { + m.chainPrerouting6 = chain + } else { + m.chainPrerouting = chain + } + return nil } @@ -909,35 +910,44 @@ func (m *AclManager) createFilterChainWithHook(name string, hookNum nftables.Cha return m.rConn.AddChain(chain) } -func (m *AclManager) createPreroutingMangle(table *nftables.Table, forV6 bool) *nftables.Chain { - polAccept := nftables.ChainPolicyAccept - chain := &nftables.Chain{ - Name: "netbird-acl-prerouting-filter", - Table: table, - Hooknum: nftables.ChainHookPrerouting, - Priority: nftables.ChainPriorityMangle, - Type: nftables.ChainTypeFilter, - Policy: &polAccept, - } - - chain = m.rConn.AddChain(chain) - +func (m *AclManager) createPreroutingMangle(forV6 bool) *nftables.Chain { + workTable := m.workTable + // Raw bytes of the wireguard interface's IPv4 address. rawIP := m.wgIface.Address().Network.IP.To4() + // Subnet mask of the wireguard interface's network. mask := m.wgIface.Address().Network.Mask + // Length of an IPv4 address addrLen := uint32(4) // source address position srcAddrOffset := uint32(12) + // destination address position dstAddrOffset := uint32(16) - nullArray := nullAddress4 + // An array representing a null address in IPv4 (0.0.0.0) + nullAddressArray := nullAddress4 + // If prerouting mangle should be created for IPv6 table, replace previously defined values with IPv6 counterparts. if forV6 { + workTable = m.workTable6 rawIP = m.wgIface.Address6().Network.IP.To16() - addrLen = 16 mask = m.wgIface.Address6().Network.Mask + addrLen = 16 srcAddrOffset = uint32(8) dstAddrOffset = uint32(24) - nullArray = nullAddress6 + nullAddressArray = nullAddress6 // corresponds to :: + } + + polAccept := nftables.ChainPolicyAccept + chain := &nftables.Chain{ + Name: "netbird-acl-prerouting-filter", + Table: workTable, + Hooknum: nftables.ChainHookPrerouting, + Priority: nftables.ChainPriorityMangle, + Type: nftables.ChainTypeFilter, + Policy: &polAccept, } + + chain = m.rConn.AddChain(chain) + ip, _ := netip.AddrFromSlice(rawIP) expressions := []expr.Any{ @@ -957,7 +967,7 @@ func (m *AclManager) createPreroutingMangle(table *nftables.Table, forV6 bool) * SourceRegister: 2, DestRegister: 2, Len: addrLen, - Xor: nullArray, + Xor: nullAddressArray, Mask: mask, }, &expr.Cmp{ @@ -987,7 +997,7 @@ func (m *AclManager) createPreroutingMangle(table *nftables.Table, forV6 bool) * }, } _ = m.rConn.AddRule(&nftables.Rule{ - Table: table, + Table: workTable, Chain: chain, Exprs: expressions, }) @@ -1034,21 +1044,31 @@ func (m *AclManager) addJumpRuleToInputChain(table *nftables.Table, chain *nftab } func (m *AclManager) addRouteAllowRule(chain *nftables.Chain, netIfName expr.MetaKey) { - ip, _ := netip.AddrFromSlice(m.wgIface.Address().Network.IP.To4()) + // Raw bytes of the wireguard interface's IPv4 address. + rawIP := m.wgIface.Address().Network.IP.To4() + // Subnet mask of the wireguard interface's network. + mask := m.wgIface.Address().Network.Mask + // Length of an IPv4 address addrLen := uint32(4) + // source address position srcAddrOffset := uint32(12) + // destination address position dstAddrOffset := uint32(16) - mask := m.wgIface.Address().Network.Mask - nullArray := nullAddress4 + // An array representing a null address in IPv4 (0.0.0.0) + nullAddressArray := nullAddress4 + + // If route allow rule should be created for IPv6 table, replace previously defined values with IPv6 counterparts. if chain.Table.Family == nftables.TableFamilyIPv6 { - ip, _ = netip.AddrFromSlice(m.wgIface.Address6().Network.IP.To16()) + rawIP = m.wgIface.Address6().Network.IP.To16() + mask = m.wgIface.Address6().Network.Mask addrLen = 16 srcAddrOffset = 8 dstAddrOffset = 24 - mask = m.wgIface.Address6().Network.Mask - nullArray = nullAddress6 + nullAddressArray = nullAddress6 // corresponds to :: } + ip, _ := netip.AddrFromSlice(rawIP) + var srcOp, dstOp expr.CmpOp if netIfName == expr.MetaKeyIIFNAME { srcOp = expr.CmpOpEq @@ -1074,7 +1094,7 @@ func (m *AclManager) addRouteAllowRule(chain *nftables.Chain, netIfName expr.Met SourceRegister: 2, DestRegister: 2, Len: addrLen, - Xor: nullArray, + Xor: nullAddressArray, Mask: mask, }, &expr.Cmp{ @@ -1092,7 +1112,7 @@ func (m *AclManager) addRouteAllowRule(chain *nftables.Chain, netIfName expr.Met SourceRegister: 2, DestRegister: 2, Len: addrLen, - Xor: nullArray, + Xor: nullAddressArray, Mask: mask, }, &expr.Cmp{ @@ -1112,21 +1132,31 @@ func (m *AclManager) addRouteAllowRule(chain *nftables.Chain, netIfName expr.Met } func (m *AclManager) addFwdAllow(chain *nftables.Chain, iifname expr.MetaKey) { - ip, _ := netip.AddrFromSlice(m.wgIface.Address().Network.IP.To4()) + // Raw bytes of the wireguard interface's IPv4 address. + rawIP := m.wgIface.Address().Network.IP.To4() + // Subnet mask of the wireguard interface's network. + mask := m.wgIface.Address().Network.Mask + // Length of an IPv4 address addrLen := uint32(4) + // source address position srcAddrOffset := uint32(12) + // destination address position dstAddrOffset := uint32(16) - mask := m.wgIface.Address().Network.Mask - nullArray := nullAddress4 + // An array representing a null address in IPv4 (0.0.0.0) + nullAddressArray := nullAddress4 + + // If forward allow rule should be created for IPv6 table, replace previously defined values with IPv6 counterparts. if chain.Table.Family == nftables.TableFamilyIPv6 { - ip, _ = netip.AddrFromSlice(m.wgIface.Address6().Network.IP.To16()) + rawIP = m.wgIface.Address6().Network.IP.To16() + mask = m.wgIface.Address6().Network.Mask addrLen = 16 srcAddrOffset = 8 dstAddrOffset = 24 - mask = m.wgIface.Address6().Network.Mask - nullArray = nullAddress6 + nullAddressArray = nullAddress6 // corresponds to :: } + ip, _ := netip.AddrFromSlice(rawIP) + var srcOp, dstOp expr.CmpOp if iifname == expr.MetaKeyIIFNAME { srcOp = expr.CmpOpNeq @@ -1152,7 +1182,7 @@ func (m *AclManager) addFwdAllow(chain *nftables.Chain, iifname expr.MetaKey) { SourceRegister: 2, DestRegister: 2, Len: addrLen, - Xor: nullArray, + Xor: nullAddressArray, Mask: mask, }, &expr.Cmp{ @@ -1170,7 +1200,7 @@ func (m *AclManager) addFwdAllow(chain *nftables.Chain, iifname expr.MetaKey) { SourceRegister: 2, DestRegister: 2, Len: addrLen, - Xor: nullArray, + Xor: nullAddressArray, Mask: mask, }, &expr.Cmp{ @@ -1190,21 +1220,31 @@ func (m *AclManager) addFwdAllow(chain *nftables.Chain, iifname expr.MetaKey) { } func (m *AclManager) addJumpRule(chain *nftables.Chain, to string, ifaceKey expr.MetaKey) { - ip, _ := netip.AddrFromSlice(m.wgIface.Address().Network.IP.To4()) + // Raw bytes of the wireguard interface's IPv4 address. + rawIP := m.wgIface.Address().Network.IP.To4() + // Subnet mask of the wireguard interface's network. + mask := m.wgIface.Address().Network.Mask + // Length of an IPv4 address addrLen := uint32(4) + // source address position srcAddrOffset := uint32(12) + // destination address position dstAddrOffset := uint32(16) - mask := m.wgIface.Address().Network.Mask - nullArray := nullAddress4 + // An array representing a null address in IPv4 (0.0.0.0) + nullAddressArray := nullAddress4 + + // If jump rule should be created for IPv6 table, replace previously defined values with IPv6 counterparts. if chain.Table.Family == nftables.TableFamilyIPv6 { - ip, _ = netip.AddrFromSlice(m.wgIface.Address6().Network.IP.To16()) + rawIP = m.wgIface.Address6().Network.IP.To16() + mask = m.wgIface.Address6().Network.Mask addrLen = 16 srcAddrOffset = 8 dstAddrOffset = 24 - mask = m.wgIface.Address6().Network.Mask - nullArray = nullAddress6 + nullAddressArray = nullAddress6 // corresponds to :: } + ip, _ := netip.AddrFromSlice(rawIP) + expressions := []expr.Any{ &expr.Meta{Key: ifaceKey, Register: 1}, &expr.Cmp{ @@ -1222,7 +1262,7 @@ func (m *AclManager) addJumpRule(chain *nftables.Chain, to string, ifaceKey expr SourceRegister: 2, DestRegister: 2, Len: addrLen, - Xor: nullArray, + Xor: nullAddressArray, Mask: mask, }, &expr.Cmp{ @@ -1240,7 +1280,7 @@ func (m *AclManager) addJumpRule(chain *nftables.Chain, to string, ifaceKey expr SourceRegister: 2, DestRegister: 2, Len: addrLen, - Xor: nullArray, + Xor: nullAddressArray, Mask: mask, }, &expr.Cmp{ @@ -1261,63 +1301,47 @@ func (m *AclManager) addJumpRule(chain *nftables.Chain, to string, ifaceKey expr } func (m *AclManager) addIpToSet(ipsetName string, ip net.IP) (*nftables.Set, error) { + + workTable := m.workTable + // Raw bytes of the IPv4 address to add rawIP := ip.To4() + // Type of set to add to ipsetType := nftables.TypeIPAddr + // IP set store to use + ipsetStorage := m.ipsetStore + + // If rawIP == nil, we have an IPv6 address, replace previously defined values with IPv6 counterparts. if rawIP == nil { + workTable = m.workTable6 rawIP = ip.To16() ipsetType = nftables.TypeIP6Addr + ipsetStorage = m.ipsetStore6 } - if ipsetType == nftables.TypeIPAddr { - ipset, err := m.rConn.GetSetByName(m.workTable, ipsetName) - if err != nil { - if ipset, err = m.createSet(m.workTable, ipsetName, ipsetType); err != nil { - return nil, fmt.Errorf("get set name: %v", err) - } - - m.ipsetStore.newIpset(ipset.Name) - } - - if m.ipsetStore.IsIpInSet(ipset.Name, ip) { - return ipset, nil - } - if err := m.sConn.SetAddElements(ipset, []nftables.SetElement{{Key: rawIP}}); err != nil { - return nil, fmt.Errorf("add set element for the first time: %v", err) + ipset, err := m.rConn.GetSetByName(workTable, ipsetName) + if err != nil { + if ipset, err = m.createSet(workTable, ipsetName, ipsetType); err != nil { + return nil, fmt.Errorf("get set name: %v", err) } - m.ipsetStore.AddIpToSet(ipset.Name, ip) - - if err := m.sConn.Flush(); err != nil { - return nil, fmt.Errorf("flush add elements: %v", err) - } + ipsetStorage.newIpset(ipset.Name) + } + if ipsetStorage.IsIpInSet(ipset.Name, ip) { return ipset, nil - } else { - ipset, err := m.rConn.GetSetByName(m.workTable6, ipsetName) - if err != nil { - if ipset, err = m.createSet(m.workTable6, ipsetName, ipsetType); err != nil { - return nil, fmt.Errorf("get set name: %v", err) - } - - m.ipsetStore6.newIpset(ipset.Name) - } - - if m.ipsetStore6.IsIpInSet(ipset.Name, ip) { - return ipset, nil - } - - if err := m.sConn.SetAddElements(ipset, []nftables.SetElement{{Key: rawIP}}); err != nil { - return nil, fmt.Errorf("add set element for the first time: %v", err) - } + } - m.ipsetStore6.AddIpToSet(ipset.Name, ip) + if err := m.sConn.SetAddElements(ipset, []nftables.SetElement{{Key: rawIP}}); err != nil { + return nil, fmt.Errorf("add set element for the first time: %v", err) + } - if err := m.sConn.Flush(); err != nil { - return nil, fmt.Errorf("flush add elements: %v", err) - } + ipsetStorage.AddIpToSet(ipset.Name, ip) - return ipset, nil + if err := m.sConn.Flush(); err != nil { + return nil, fmt.Errorf("flush add elements: %v", err) } + + return ipset, nil } // createSet in given table by name @@ -1419,10 +1443,14 @@ func generateRuleIdForMangle(ipset *nftables.Set, ip net.IP, proto firewall.Prot if port != nil { p = port.String() } + ipver := "v4" + if ipset.Table.Family == nftables.TableFamilyIPv6 { + ipver = "v6" + } if ipset != nil { - return fmt.Sprintf("p:set:%s:%s:%v", ipset.Name, proto, p) + return fmt.Sprintf("p:set:%s:%s:%s:%v", ipver, ipset.Name, proto, p) } else { - return fmt.Sprintf("p:ip:%s:%s:%v", ip.String(), proto, p) + return fmt.Sprintf("p:ip:%s:%s:%s:%v", ipver, ip.String(), proto, p) } } diff --git a/client/firewall/nftables/manager_linux.go b/client/firewall/nftables/manager_linux.go index c047fb9e91f..b33bc7f599f 100644 --- a/client/firewall/nftables/manager_linux.go +++ b/client/firewall/nftables/manager_linux.go @@ -36,14 +36,14 @@ func Create(context context.Context, wgIface iFaceMapper) (*Manager, error) { wgIface: wgIface, } - workTable, err := m.createWorkTable() + workTable, err := m.createWorkTable(nftables.TableFamilyIPv4) if err != nil { return nil, err } var workTable6 *nftables.Table if wgIface.Address6() != nil { - workTable6, err = m.createWorkTable6() + workTable6, err = m.createWorkTable(nftables.TableFamilyIPv6) if err != nil { return nil, err } @@ -65,31 +65,36 @@ func Create(context context.Context, wgIface iFaceMapper) (*Manager, error) { // Resets the IPv6 Firewall Table to adapt to changes in IP addresses func (m *Manager) ResetV6Firewall() error { + // First, prepare reset by deleting all currently active rules. workTable6, err := m.aclManager.PrepareV6Reset() if err != nil { return err } + // Depending on whether we now have an IPv6 address, we now either have to create/empty an IPv6 table, or delete it. if m.wgIface.Address6() != nil { if workTable6 != nil { m.rConn.FlushTable(workTable6) } else { - workTable6, err = m.createWorkTable6() - m.rConn.Flush() - if err != nil { - return err - } + workTable6, err = m.createWorkTable(nftables.TableFamilyIPv6) } } else { m.rConn.DelTable(workTable6) workTable6 = nil } + err = m.rConn.Flush() + if err != nil { + return err + } + // Restore routing rules. err = m.router.RestoreAfterV6Reset(workTable6) if err != nil { return err } + // Restore basic firewall chains (needs to happen after routes because chains from router must exist). + // Does not restore rules (will be done later during the update, when UpdateFiltering will be called at some point) err = m.aclManager.ReinitAfterV6Reset(workTable6) if err != nil { return err @@ -162,6 +167,8 @@ func (m *Manager) AllowNetbird() error { m.mutex.Lock() defer m.mutex.Unlock() + // Note for devs: When adding IPv6 support to uspfilter, the implementation of createDefaultAllowRules() + // must be adjusted to include IPv6 rules. err := m.aclManager.createDefaultAllowRules() if err != nil { return fmt.Errorf("failed to create default allow rules: %v", err) @@ -259,8 +266,8 @@ func (m *Manager) Flush() error { return m.aclManager.Flush() } -func (m *Manager) createWorkTable() (*nftables.Table, error) { - tables, err := m.rConn.ListTablesOfFamily(nftables.TableFamilyIPv4) +func (m *Manager) createWorkTable(tableFamily nftables.TableFamily) (*nftables.Table, error) { + tables, err := m.rConn.ListTablesOfFamily(tableFamily) if err != nil { return nil, fmt.Errorf("list of tables: %w", err) } @@ -271,28 +278,11 @@ func (m *Manager) createWorkTable() (*nftables.Table, error) { } } - table := m.rConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv4}) + table := m.rConn.AddTable(&nftables.Table{Name: tableName, Family: tableFamily}) err = m.rConn.Flush() return table, err } -func (m *Manager) createWorkTable6() (*nftables.Table, error) { - tables6, err := m.rConn.ListTablesOfFamily(nftables.TableFamilyIPv6) - if err != nil { - return nil, fmt.Errorf("list of v6 tables: %w", err) - } - - for _, t := range tables6 { - if t.Name == tableName { - m.rConn.DelTable(t) - } - } - - table6 := m.rConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv6}) - err = m.rConn.Flush() - return table6, err -} - func (m *Manager) applyAllowNetbirdRules(chain *nftables.Chain) { rule := &nftables.Rule{ Table: chain.Table, diff --git a/client/firewall/nftables/route_linux.go b/client/firewall/nftables/route_linux.go index ebbb7d137e3..0d82c3a1d12 100644 --- a/client/firewall/nftables/route_linux.go +++ b/client/firewall/nftables/route_linux.go @@ -81,23 +81,23 @@ func newRouter(parentCtx context.Context, workTable *nftables.Table, workTable6 } } - err = r.cleanUpDefaultForwardRules() + err = r.cleanUpDefaultForwardRules(false) if err != nil { log.Errorf("failed to clean up rules from FORWARD chain: %s", err) } - err = r.cleanUpDefaultForwardRules6() + err = r.cleanUpDefaultForwardRules(true) if err != nil { log.Errorf("failed to clean up rules from IPv6 FORWARD chain: %s", err) } - err = r.createContainers() + err = r.createContainers(false) if err != nil { log.Errorf("failed to create containers for route: %s", err) } if r.workTable6 != nil { - err = r.createContainers6() + err = r.createContainers(true) if err != nil { log.Errorf("failed to create v6 containers for route: %s", err) } @@ -112,7 +112,11 @@ func (r *router) RouteingFwChainName() string { // ResetForwardRules cleans existing nftables default forward rules from the system func (r *router) ResetForwardRules() { - err := r.cleanUpDefaultForwardRules() + err := r.cleanUpDefaultForwardRules(false) + if err != nil { + log.Errorf("failed to reset forward rules: %s", err) + } + err = r.cleanUpDefaultForwardRules(true) if err != nil { log.Errorf("failed to reset forward rules: %s", err) } @@ -122,12 +126,12 @@ func (r *router) RestoreAfterV6Reset(newWorktable6 *nftables.Table) error { r.workTable6 = newWorktable6 if newWorktable6 != nil { - err := r.cleanUpDefaultForwardRules6() + err := r.cleanUpDefaultForwardRules(true) if err != nil { log.Errorf("failed to clean up rules from IPv6 FORWARD chain: %s", err) } - err = r.createContainers6() + err = r.createContainers(true) if err != nil { return err } @@ -179,48 +183,28 @@ func (r *router) loadFilterTables() (*nftables.Table, *nftables.Table, error) { return table4, table6, err } -func (r *router) createContainers() error { - - r.chains[chainNameRouteingFw] = r.conn.AddChain(&nftables.Chain{ - Name: chainNameRouteingFw, - Table: r.workTable, - }) - - r.chains[chainNameRoutingNat] = r.conn.AddChain(&nftables.Chain{ - Name: chainNameRoutingNat, - Table: r.workTable, - Hooknum: nftables.ChainHookPostrouting, - Priority: nftables.ChainPriorityNATSource - 1, - Type: nftables.ChainTypeNAT, - }) - - err := r.refreshRulesMap() - if err != nil { - log.Errorf("failed to clean up rules from FORWARD chain: %s", err) +func (r *router) createContainers(forV6 bool) error { + workTable := r.workTable + chainStorage := r.chains + if forV6 { + workTable = r.workTable6 + chainStorage = r.chains6 } - err = r.conn.Flush() - if err != nil { - return fmt.Errorf("nftables: unable to initialize table: %v", err) - } - return nil -} -func (r *router) createContainers6() error { - - r.chains6[chainNameRouteingFw] = r.conn.AddChain(&nftables.Chain{ + chainStorage[chainNameRouteingFw] = r.conn.AddChain(&nftables.Chain{ Name: chainNameRouteingFw, - Table: r.workTable6, + Table: workTable, }) - r.chains6[chainNameRoutingNat] = r.conn.AddChain(&nftables.Chain{ + chainStorage[chainNameRoutingNat] = r.conn.AddChain(&nftables.Chain{ Name: chainNameRoutingNat, - Table: r.workTable6, + Table: workTable, Hooknum: nftables.ChainHookPostrouting, Priority: nftables.ChainPriorityNATSource - 1, Type: nftables.ChainTypeNAT, }) - err := r.refreshRulesMap6() + err := r.refreshRulesMap(forV6) if err != nil { log.Errorf("failed to clean up rules from FORWARD chain: %s", err) } @@ -240,7 +224,7 @@ func (r *router) InsertRoutingRules(pair manager.RouterPair) error { return fmt.Errorf("nftables: attempted to add IPv6 routing rule even though IPv6 is not enabled for this host") } - err := r.refreshRulesMap() + err := r.refreshRulesMap(parsedIp.To4() == nil) if err != nil { return err } @@ -295,7 +279,13 @@ func (r *router) insertRoutingRule(format, chainName string, pair manager.Router ruleKey := manager.GenKey(format, pair.ID) - _, exists := r.rules[ruleKey] + parsedIp, _, _ := net.ParseCIDR(pair.Source) + rules := r.rules + if parsedIp.To4() == nil { + rules = r.rules6 + } + + _, exists := rules[ruleKey] if exists { err := r.removeRoutingRule(format, pair) if err != nil { @@ -303,18 +293,23 @@ func (r *router) insertRoutingRule(format, chainName string, pair manager.Router } } - table, chain, rules := r.workTable, r.chains[chainName], r.rules - parsedIp, _, _ := net.ParseCIDR(pair.Source) + table, chain := r.workTable, r.chains[chainName] if parsedIp.To4() == nil { - table, chain, rules = r.workTable6, r.chains6[chainName], r.rules6 + table, chain = r.workTable6, r.chains6[chainName] } - rules[ruleKey] = r.conn.InsertRule(&nftables.Rule{ + newRule := r.conn.InsertRule(&nftables.Rule{ Table: table, Chain: chain, Exprs: expression, UserData: []byte(ruleKey), }) + + if parsedIp.To4() == nil { + r.rules[ruleKey] = newRule + } else { + r.rules6[ruleKey] = newRule + } return nil } @@ -385,7 +380,7 @@ func (r *router) RemoveRoutingRules(pair manager.RouterPair) error { return fmt.Errorf("nftables: attempted to remove IPv6 routing rule even though IPv6 is not enabled for this host") } - err := r.refreshRulesMap() + err := r.refreshRulesMap(parsedIp.To4() == nil) if err != nil { return err } @@ -410,8 +405,12 @@ func (r *router) RemoveRoutingRules(pair manager.RouterPair) error { return err } - if len(r.rules) == 0 { - err := r.cleanUpDefaultForwardRules() + rulesList := r.rules + if parsedIp.To4() == nil { + rulesList = r.rules6 + } + if len(rulesList) == 0 { + err := r.cleanUpDefaultForwardRules(parsedIp.To4() == nil) if err != nil { log.Errorf("failed to clean up rules from FORWARD chain: %s", err) } @@ -450,100 +449,64 @@ func (r *router) removeRoutingRule(format string, pair manager.RouterPair) error // refreshRulesMap refreshes the rule map with the latest rules. this is useful to avoid // duplicates and to get missing attributes that we don't have when adding new rules -func (r *router) refreshRulesMap() error { - for _, chain := range r.chains { - rules, err := r.conn.GetRules(chain.Table, chain) - if err != nil { - return fmt.Errorf("nftables: unable to list rules: %v", err) - } - for _, rule := range rules { - if len(rule.UserData) > 0 { - r.rules[string(rule.UserData)] = rule - } - } +func (r *router) refreshRulesMap(forV6 bool) error { + chainList := r.chains + if forV6 { + chainList = r.chains6 } - return nil -} - -// refreshRulesMap6 refreshes the rule map for IPv6 with the latest rules. this is useful to avoid -// duplicates and to get missing attributes that we don't have when adding new rules -func (r *router) refreshRulesMap6() error { - for _, chain := range r.chains6 { + for _, chain := range chainList { rules, err := r.conn.GetRules(chain.Table, chain) if err != nil { return fmt.Errorf("nftables: unable to list rules: %v", err) } for _, rule := range rules { if len(rule.UserData) > 0 { - r.rules6[string(rule.UserData)] = rule + if forV6 { + r.rules6[string(rule.UserData)] = rule + } else { + r.rules[string(rule.UserData)] = rule + } } } } return nil } -func (r *router) cleanUpDefaultForwardRules() error { - if r.filterTable == nil { - r.isDefaultFwdRulesEnabled = false - return nil - } - - chains, err := r.conn.ListChainsOfTableFamily(nftables.TableFamilyIPv4) - if err != nil { - return err - } - - var rules []*nftables.Rule - for _, chain := range chains { - if chain.Table.Name != r.filterTable.Name { - continue - } - if chain.Name != "FORWARD" { - continue - } - - rules, err = r.conn.GetRules(r.filterTable, chain) - if err != nil { - return err - } +func (r *router) cleanUpDefaultForwardRules(forV6 bool) error { + tableFamily := nftables.TableFamilyIPv4 + filterTable := r.filterTable + if forV6 { + tableFamily = nftables.TableFamilyIPv6 + filterTable = r.filterTable6 } - for _, rule := range rules { - if bytes.Equal(rule.UserData, []byte(userDataAcceptForwardRuleSrc)) || bytes.Equal(rule.UserData, []byte(userDataAcceptForwardRuleDst)) { - err := r.conn.DelRule(rule) - if err != nil { - return err - } + if filterTable == nil { + if forV6 { + r.isDefaultFwdRulesEnabled6 = false + } else { + r.isDefaultFwdRulesEnabled = false } - } - r.isDefaultFwdRulesEnabled = false - return r.conn.Flush() -} -func (r *router) cleanUpDefaultForwardRules6() error { - if r.filterTable6 == nil { - r.isDefaultFwdRulesEnabled6 = false return nil } - chains, err := r.conn.ListChainsOfTableFamily(nftables.TableFamilyIPv6) + chains, err := r.conn.ListChainsOfTableFamily(tableFamily) if err != nil { return err } var rules []*nftables.Rule for _, chain := range chains { - if chain.Table.Name != r.filterTable6.Name { + if chain.Table.Name != filterTable.Name { continue } if chain.Name != "FORWARD" { continue } - rules6, err := r.conn.GetRules(r.filterTable6, chain) + rules, err = r.conn.GetRules(filterTable, chain) if err != nil { return err } - rules = rules6 } for _, rule := range rules { @@ -554,7 +517,12 @@ func (r *router) cleanUpDefaultForwardRules6() error { } } } - r.isDefaultFwdRulesEnabled6 = false + + if forV6 { + r.isDefaultFwdRulesEnabled6 = false + } else { + r.isDefaultFwdRulesEnabled = false + } return r.conn.Flush() } diff --git a/client/internal/acl/manager.go b/client/internal/acl/manager.go index f2adc1b18c3..50bd9dd2277 100644 --- a/client/internal/acl/manager.go +++ b/client/internal/acl/manager.go @@ -400,7 +400,8 @@ func (d *DefaultManager) squashAcceptRules( // it means that rules for that protocol was already optimized on the // management side if r.PeerIP == "0.0.0.0" { - // TODO IPv6? + // I don't _think_ that IPv6 is relevant here, as any optimization that has r.PeerIP6 == "::" should also + // implicitly have r.PeerIP == "0.0.0.0". squashedRules = append(squashedRules, r) squashedProtocols[r.Protocol] = struct{}{} return diff --git a/client/internal/routemanager/manager.go b/client/internal/routemanager/manager.go index d9e22b80442..3496dbdaa35 100644 --- a/client/internal/routemanager/manager.go +++ b/client/internal/routemanager/manager.go @@ -111,10 +111,6 @@ func (m *DefaultManager) ResetV6Routes() { delete(m.clientNetworks, id) } } - - if m.serverRouter != nil { - m.serverRouter.handleV6FirewallReset() - } } // SetRouteChangeListener set RouteListener for route change notifier diff --git a/client/internal/routemanager/server.go b/client/internal/routemanager/server.go index 99ff44903d8..c9a13a90414 100644 --- a/client/internal/routemanager/server.go +++ b/client/internal/routemanager/server.go @@ -5,6 +5,5 @@ import "github.com/netbirdio/netbird/route" type serverRouter interface { updateRoutes(map[string]*route.Route) error removeFromServerNetwork(*route.Route) error - handleV6FirewallReset() cleanUp() } diff --git a/client/internal/routemanager/server_nonandroid.go b/client/internal/routemanager/server_nonandroid.go index e1cb5ab38a2..6ad14bc218b 100644 --- a/client/internal/routemanager/server_nonandroid.go +++ b/client/internal/routemanager/server_nonandroid.go @@ -67,7 +67,7 @@ func (m *defaultServerRouter) updateRoutes(routesMap map[string]*route.Route) er } if len(m.routes) > 0 { - err := enableIPForwarding() + err := enableIPForwarding(m.wgInterface.Address6() != nil) if err != nil { return err } @@ -76,12 +76,6 @@ func (m *defaultServerRouter) updateRoutes(routesMap map[string]*route.Route) er return nil } -// Handles a reset of the IPv6 firewall table (necessary if IPv6 address changes). -func (m *defaultServerRouter) handleV6FirewallReset() { - m.mux.Lock() - defer m.mux.Unlock() -} - func (m *defaultServerRouter) removeFromServerNetwork(rt *route.Route) error { select { case <-m.ctx.Done(): diff --git a/client/internal/routemanager/systemops_linux.go b/client/internal/routemanager/systemops_linux.go index 4010c6af928..493f1dd5e05 100644 --- a/client/internal/routemanager/systemops_linux.go +++ b/client/internal/routemanager/systemops_linux.go @@ -166,7 +166,7 @@ loop: return prefixList, nil } -func enableIPForwarding() error { +func enableIPForwarding(includeV6 bool) error { bytes, err := os.ReadFile(ipv4ForwardingPath) if err != nil { return err @@ -181,13 +181,16 @@ func enableIPForwarding() error { } } - // Do the same for IPv6 - bytes, err = os.ReadFile(ipv6ForwardingPath) - if err != nil { - return err - } - if len(bytes) > 0 && bytes[0] == 49 { - return nil + if includeV6 { + // Do the same for IPv6 + bytes, err = os.ReadFile(ipv6ForwardingPath) + if err != nil { + return err + } + if len(bytes) > 0 && bytes[0] == 49 { + return nil + } + return os.WriteFile(ipv6ForwardingPath, []byte("1"), 0644) //nolint:gosec } - return os.WriteFile(ipv6ForwardingPath, []byte("1"), 0644) //nolint:gosec + return nil } From 0173c20da21f9572bfe70cf529e4399a2286d524 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Thu, 7 Mar 2024 21:44:05 +0100 Subject: [PATCH 29/42] Fix nil dereference in rule mangling and compilation issues --- client/firewall/nftables/acl_linux.go | 2 +- client/internal/routemanager/systemops_nonlinux.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/firewall/nftables/acl_linux.go b/client/firewall/nftables/acl_linux.go index 68bc28094f9..2c68b58a412 100644 --- a/client/firewall/nftables/acl_linux.go +++ b/client/firewall/nftables/acl_linux.go @@ -1444,7 +1444,7 @@ func generateRuleIdForMangle(ipset *nftables.Set, ip net.IP, proto firewall.Prot p = port.String() } ipver := "v4" - if ipset.Table.Family == nftables.TableFamilyIPv6 { + if (ipset != nil && ipset.Table.Family == nftables.TableFamilyIPv6) || ip.To4() == nil { ipver = "v6" } if ipset != nil { diff --git a/client/internal/routemanager/systemops_nonlinux.go b/client/internal/routemanager/systemops_nonlinux.go index 91965e656b0..d12d20ba16f 100644 --- a/client/internal/routemanager/systemops_nonlinux.go +++ b/client/internal/routemanager/systemops_nonlinux.go @@ -37,7 +37,7 @@ func removeFromRouteTable(prefix netip.Prefix, addr string, devName string) erro return nil } -func enableIPForwarding() error { +func enableIPForwarding(forV6 bool) error { log.Infof("enable IP forwarding is not implemented on %s", runtime.GOOS) return nil } From 03aa74bc6ed8a8bba17a2fa9345a0c296e612cdf Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Fri, 8 Mar 2024 05:20:29 +0100 Subject: [PATCH 30/42] Add a bunch of client-side test cases for IPv6 --- client/firewall/nftables/manager_linux.go | 3 + client/firewall/nftables/route_linux.go | 10 +- client/firewall/nftables/router_linux_test.go | 50 +- client/firewall/test/cases_linux.go | 33 +- client/internal/routemanager/manager_test.go | 431 +++++++++++++++++- .../internal/routemanager/systemops_linux.go | 10 +- .../routemanager/systemops_nonandroid.go | 27 ++ .../routemanager/systemops_nonandroid_test.go | 151 +++++- 8 files changed, 683 insertions(+), 32 deletions(-) diff --git a/client/firewall/nftables/manager_linux.go b/client/firewall/nftables/manager_linux.go index b33bc7f599f..6fc3e4ebbff 100644 --- a/client/firewall/nftables/manager_linux.go +++ b/client/firewall/nftables/manager_linux.go @@ -77,6 +77,9 @@ func (m *Manager) ResetV6Firewall() error { m.rConn.FlushTable(workTable6) } else { workTable6, err = m.createWorkTable(nftables.TableFamilyIPv6) + if err != nil { + return err + } } } else { m.rConn.DelTable(workTable6) diff --git a/client/firewall/nftables/route_linux.go b/client/firewall/nftables/route_linux.go index 0d82c3a1d12..862d3c24de2 100644 --- a/client/firewall/nftables/route_linux.go +++ b/client/firewall/nftables/route_linux.go @@ -428,7 +428,13 @@ func (r *router) RemoveRoutingRules(pair manager.RouterPair) error { func (r *router) removeRoutingRule(format string, pair manager.RouterPair) error { ruleKey := manager.GenKey(format, pair.ID) - rule, found := r.rules[ruleKey] + parsedIp, _, _ := net.ParseCIDR(pair.Source) + rules := r.rules + if parsedIp.To4() == nil { + rules = r.rules6 + } + + rule, found := rules[ruleKey] if found { ruleType := "forwarding" if rule.Chain.Type == nftables.ChainTypeNAT { @@ -442,7 +448,7 @@ func (r *router) removeRoutingRule(format string, pair manager.RouterPair) error log.Debugf("nftables: removing %s rule for %s", ruleType, pair.Destination) - delete(r.rules, ruleKey) + delete(rules, ruleKey) } return nil } diff --git a/client/firewall/nftables/router_linux_test.go b/client/firewall/nftables/router_linux_test.go index 253c0356af3..83facd59855 100644 --- a/client/firewall/nftables/router_linux_test.go +++ b/client/firewall/nftables/router_linux_test.go @@ -34,7 +34,7 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) { t.Fatal(err) } - defer deleteWorkTable() + defer deleteWorkTables() for _, testCase := range test.InsertRuleTestCases { t.Run(testCase.Name, func(t *testing.T) { @@ -58,8 +58,13 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) { testingExpression := append(sourceExp, destExp...) //nolint:gocritic fwdRuleKey := firewall.GenKey(firewall.ForwardingFormat, testCase.InputPair.ID) + chains := manager.chains + if testCase.IsV6 { + chains = manager.chains6 + } + found := 0 - for _, chain := range manager.chains { + for _, chain := range chains { rules, err := nftablesTestingClient.GetRules(chain.Table, chain) require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name) for _, rule := range rules { @@ -75,7 +80,7 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) { if testCase.InputPair.Masquerade { natRuleKey := firewall.GenKey(firewall.NatFormat, testCase.InputPair.ID) found := 0 - for _, chain := range manager.chains { + for _, chain := range chains { rules, err := nftablesTestingClient.GetRules(chain.Table, chain) require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name) for _, rule := range rules { @@ -94,7 +99,7 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) { inFwdRuleKey := firewall.GenKey(firewall.InForwardingFormat, testCase.InputPair.ID) found = 0 - for _, chain := range manager.chains { + for _, chain := range chains { rules, err := nftablesTestingClient.GetRules(chain.Table, chain) require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name) for _, rule := range rules { @@ -110,7 +115,7 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) { if testCase.InputPair.Masquerade { inNatRuleKey := firewall.GenKey(firewall.InNatFormat, testCase.InputPair.ID) found := 0 - for _, chain := range manager.chains { + for _, chain := range chains { rules, err := nftablesTestingClient.GetRules(chain.Table, chain) require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name) for _, rule := range rules { @@ -136,7 +141,7 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) { t.Fatal(err) } - defer deleteWorkTable() + defer deleteWorkTables() for _, testCase := range test.RemoveRuleTestCases { t.Run(testCase.Name, func(t *testing.T) { @@ -150,11 +155,18 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) { sourceExp := generateCIDRMatcherExpressions(true, testCase.InputPair.Source) destExp := generateCIDRMatcherExpressions(false, testCase.InputPair.Destination) + chains := manager.chains + workTable := table + if testCase.IsV6 { + chains = manager.chains6 + workTable = table6 + } + forwardExp := append(sourceExp, append(destExp, exprCounterAccept...)...) //nolint:gocritic forwardRuleKey := firewall.GenKey(firewall.ForwardingFormat, testCase.InputPair.ID) insertedForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{ - Table: manager.workTable, - Chain: manager.chains[chainNameRouteingFw], + Table: workTable, + Chain: chains[chainNameRouteingFw], Exprs: forwardExp, UserData: []byte(forwardRuleKey), }) @@ -163,8 +175,8 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) { natRuleKey := firewall.GenKey(firewall.NatFormat, testCase.InputPair.ID) insertedNat := nftablesTestingClient.InsertRule(&nftables.Rule{ - Table: manager.workTable, - Chain: manager.chains[chainNameRoutingNat], + Table: workTable, + Chain: chains[chainNameRoutingNat], Exprs: natExp, UserData: []byte(natRuleKey), }) @@ -175,8 +187,8 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) { forwardExp = append(sourceExp, append(destExp, exprCounterAccept...)...) //nolint:gocritic inForwardRuleKey := firewall.GenKey(firewall.InForwardingFormat, testCase.InputPair.ID) insertedInForwarding := nftablesTestingClient.InsertRule(&nftables.Rule{ - Table: manager.workTable, - Chain: manager.chains[chainNameRouteingFw], + Table: workTable, + Chain: chains[chainNameRouteingFw], Exprs: forwardExp, UserData: []byte(inForwardRuleKey), }) @@ -185,8 +197,8 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) { inNatRuleKey := firewall.GenKey(firewall.InNatFormat, testCase.InputPair.ID) insertedInNat := nftablesTestingClient.InsertRule(&nftables.Rule{ - Table: manager.workTable, - Chain: manager.chains[chainNameRoutingNat], + Table: workTable, + Chain: chains[chainNameRoutingNat], Exprs: natExp, UserData: []byte(inNatRuleKey), }) @@ -199,7 +211,7 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) { err = manager.RemoveRoutingRules(testCase.InputPair) require.NoError(t, err, "shouldn't return error") - for _, chain := range manager.chains { + for _, chain := range chains { rules, err := nftablesTestingClient.GetRules(chain.Table, chain) require.NoError(t, err, "should list rules for %s table and %s chain", chain.Table.Name, chain.Name) for _, rule := range rules { @@ -267,7 +279,7 @@ func createWorkTables() (*nftables.Table, *nftables.Table, error) { return table, table6, err } -func deleteWorkTable() { +func deleteWorkTables() { sConn, err := nftables.New(nftables.AsLasting()) if err != nil { return @@ -278,6 +290,12 @@ func deleteWorkTable() { return } + tables6, err := sConn.ListTablesOfFamily(nftables.TableFamilyIPv6) + if err != nil { + return + } + tables = append(tables, tables6...) + for _, t := range tables { if t.Name == tableName { sConn.DelTable(t) diff --git a/client/firewall/test/cases_linux.go b/client/firewall/test/cases_linux.go index 432d113dd46..acc71a92ce3 100644 --- a/client/firewall/test/cases_linux.go +++ b/client/firewall/test/cases_linux.go @@ -8,6 +8,7 @@ var ( InsertRuleTestCases = []struct { Name string InputPair firewall.RouterPair + IsV6 bool }{ { Name: "Insert Forwarding IPV4 Rule", @@ -27,12 +28,32 @@ var ( Masquerade: true, }, }, + { + Name: "Insert Forwarding IPV6 Rule", + InputPair: firewall.RouterPair{ + ID: "zxa", + Source: "2001:db8:0123:4567::1/128", + Destination: "2001:db8:0123:abcd::/64", + Masquerade: false, + }, + IsV6: true, + }, + { + Name: "Insert Forwarding And Nat IPV6 Rules", + InputPair: firewall.RouterPair{ + ID: "zxa", + Source: "2001:db8:0123:4567::1/128", + Destination: "2001:db8:0123:abcd::/64", + Masquerade: true, + }, + IsV6: true, + }, } RemoveRuleTestCases = []struct { Name string InputPair firewall.RouterPair - IpVersion string + IsV6 bool }{ { Name: "Remove Forwarding And Nat IPV4 Rules", @@ -43,5 +64,15 @@ var ( Masquerade: true, }, }, + { + Name: "Remove Forwarding And Nat IPV6 Rules", + InputPair: firewall.RouterPair{ + ID: "zxa", + Source: "2001:db8:0123:4567::1/128", + Destination: "2001:db8:0123:abcd::/64", + Masquerade: true, + }, + IsV6: true, + }, } ) diff --git a/client/internal/routemanager/manager_test.go b/client/internal/routemanager/manager_test.go index b5a568d1f5c..7bbcd7593ba 100644 --- a/client/internal/routemanager/manager_test.go +++ b/client/internal/routemanager/manager_test.go @@ -35,6 +35,7 @@ func TestManagerUpdateRoutes(t *testing.T) { removeSrvRouter bool serverRoutesExpected int clientNetworkWatchersExpected int + isV6 bool }{ { name: "Should create 2 client networks", @@ -64,6 +65,35 @@ func TestManagerUpdateRoutes(t *testing.T) { inputSerial: 1, clientNetworkWatchersExpected: 2, }, + { + name: "Should create 2 client networks (IPv6)", + inputInitRoutes: []*route.Route{}, + inputRoutes: []*route.Route{ + { + ID: "a", + NetID: "routeA", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "b", + NetID: "routeB", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8::7890:abcd/128"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + }, + inputSerial: 1, + clientNetworkWatchersExpected: 2, + isV6: true, + }, { name: "Should Create 2 Server Routes", inputRoutes: []*route.Route{ @@ -92,6 +122,34 @@ func TestManagerUpdateRoutes(t *testing.T) { serverRoutesExpected: 2, clientNetworkWatchersExpected: 0, }, + { + name: "Should Create 2 Server Routes (IPv6)", + inputRoutes: []*route.Route{ + { + ID: "a", + NetID: "routeA", + Peer: localPeerKey, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "b", + NetID: "routeB", + Peer: localPeerKey, + Network: netip.MustParsePrefix("2001:db8::7890:abcd/128"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + }, + inputSerial: 1, + serverRoutesExpected: 2, + clientNetworkWatchersExpected: 0, + }, { name: "Should Create 1 Route For Client And Server", inputRoutes: []*route.Route{ @@ -120,6 +178,84 @@ func TestManagerUpdateRoutes(t *testing.T) { serverRoutesExpected: 1, clientNetworkWatchersExpected: 1, }, + { + name: "Should Create 1 Route For Client And Server (IPv6)", + inputRoutes: []*route.Route{ + { + ID: "a", + NetID: "routeA", + Peer: localPeerKey, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "b", + NetID: "routeB", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8::7890:abcd/128"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + }, + inputSerial: 1, + serverRoutesExpected: 1, + clientNetworkWatchersExpected: 1, + isV6: true, + }, + { + name: "Should Create 1 Route For Client And Server for each IP version", + inputRoutes: []*route.Route{ + { + ID: "a", + NetID: "routeA", + Peer: localPeerKey, + Network: netip.MustParsePrefix("100.64.30.250/30"), + NetworkType: route.IPv4Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "b", + NetID: "routeB", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("8.8.9.9/32"), + NetworkType: route.IPv4Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "a", + NetID: "routeA", + Peer: localPeerKey, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "b", + NetID: "routeB", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8::7890:abcd/128"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + }, + inputSerial: 1, + serverRoutesExpected: 2, + clientNetworkWatchersExpected: 2, + isV6: true, + }, { name: "Should Create 1 Route For Client and Skip Server Route On Empty Server Router", inputRoutes: []*route.Route{ @@ -149,6 +285,36 @@ func TestManagerUpdateRoutes(t *testing.T) { serverRoutesExpected: 0, clientNetworkWatchersExpected: 1, }, + { + name: "Should Create 1 Route For Client and Skip Server Route On Empty Server Router (IPv6)", + inputRoutes: []*route.Route{ + { + ID: "a", + NetID: "routeA", + Peer: localPeerKey, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "b", + NetID: "routeB", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8::7890:abcd/128"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + }, + inputSerial: 1, + removeSrvRouter: true, + serverRoutesExpected: 0, + clientNetworkWatchersExpected: 1, + isV6: true, + }, { name: "Should Create 1 HA Route and 1 Standalone", inputRoutes: []*route.Route{ @@ -186,6 +352,44 @@ func TestManagerUpdateRoutes(t *testing.T) { inputSerial: 1, clientNetworkWatchersExpected: 2, }, + { + name: "Should Create 1 HA Route and 1 Standalone (IPv6)", + inputRoutes: []*route.Route{ + { + ID: "a", + NetID: "routeA", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "b", + NetID: "routeA", + Peer: remotePeerKey2, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "c", + NetID: "routeB", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8::7890:abcd/128"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + }, + inputSerial: 1, + clientNetworkWatchersExpected: 2, + isV6: true, + }, { name: "No Small Client Route Should Be Added", inputRoutes: []*route.Route{ @@ -203,6 +407,24 @@ func TestManagerUpdateRoutes(t *testing.T) { inputSerial: 1, clientNetworkWatchersExpected: 0, }, + { + name: "No Small Client Route Should Be Added (IPv6)", + inputRoutes: []*route.Route{ + { + ID: "a", + NetID: "routeA", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("::/0"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + }, + inputSerial: 1, + clientNetworkWatchersExpected: 0, + isV6: true, + }, { name: "Remove 1 Client Route", inputInitRoutes: []*route.Route{ @@ -242,6 +464,46 @@ func TestManagerUpdateRoutes(t *testing.T) { inputSerial: 1, clientNetworkWatchersExpected: 1, }, + { + name: "Remove 1 Client Route (IPv6)", + inputInitRoutes: []*route.Route{ + { + ID: "a", + NetID: "routeA", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "b", + NetID: "routeB", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8::abcd:7890/128"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + }, + inputRoutes: []*route.Route{ + { + ID: "a", + NetID: "routeA", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + }, + inputSerial: 1, + clientNetworkWatchersExpected: 1, + isV6: true, + }, { name: "Update Route to HA", inputInitRoutes: []*route.Route{ @@ -291,6 +553,56 @@ func TestManagerUpdateRoutes(t *testing.T) { inputSerial: 1, clientNetworkWatchersExpected: 1, }, + { + name: "Update Route to HA (IPv6)", + inputInitRoutes: []*route.Route{ + { + ID: "a", + NetID: "routeA", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "b", + NetID: "routeB", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8::abcd:7890/128"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + }, + inputRoutes: []*route.Route{ + { + ID: "a", + NetID: "routeA", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "b", + NetID: "routeA", + Peer: remotePeerKey2, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + }, + inputSerial: 1, + clientNetworkWatchersExpected: 1, + isV6: true, + }, { name: "Remove Client Routes", inputInitRoutes: []*route.Route{ @@ -319,6 +631,35 @@ func TestManagerUpdateRoutes(t *testing.T) { inputSerial: 1, clientNetworkWatchersExpected: 0, }, + { + name: "Remove Client Routes (IPv6)", + inputInitRoutes: []*route.Route{ + { + ID: "a", + NetID: "routeA", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "b", + NetID: "routeB", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8::abcd:7890/128"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + }, + inputRoutes: []*route.Route{}, + inputSerial: 1, + clientNetworkWatchersExpected: 0, + isV6: true, + }, { name: "Remove All Routes", inputInitRoutes: []*route.Route{ @@ -348,6 +689,36 @@ func TestManagerUpdateRoutes(t *testing.T) { serverRoutesExpected: 0, clientNetworkWatchersExpected: 0, }, + { + name: "Remove All Routes (IPv6)", + inputInitRoutes: []*route.Route{ + { + ID: "a", + NetID: "routeA", + Peer: localPeerKey, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "b", + NetID: "routeB", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8::abcd:7890/128"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + }, + inputRoutes: []*route.Route{}, + inputSerial: 1, + serverRoutesExpected: 0, + clientNetworkWatchersExpected: 0, + isV6: true, + }, { name: "HA server should not register routes from the same HA group", inputRoutes: []*route.Route{ @@ -396,16 +767,74 @@ func TestManagerUpdateRoutes(t *testing.T) { serverRoutesExpected: 2, clientNetworkWatchersExpected: 1, }, + { + name: "HA server should not register routes from the same HA group (IPv6)", + inputRoutes: []*route.Route{ + { + ID: "l1", + NetID: "routeA", + Peer: localPeerKey, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "l2", + NetID: "routeA", + Peer: localPeerKey, + Network: netip.MustParsePrefix("2001:db8::abcd:7890/128"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "r1", + NetID: "routeA", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + { + ID: "r2", + NetID: "routeC", + Peer: remotePeerKey1, + Network: netip.MustParsePrefix("2001:db8::abcd:789f/128"), + NetworkType: route.IPv6Network, + Metric: 9999, + Masquerade: false, + Enabled: true, + }, + }, + inputSerial: 1, + serverRoutesExpected: 2, + clientNetworkWatchersExpected: 1, + isV6: true, + }, } for n, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { + + v6Addr := "" + //goland:noinspection GoBoolExpressions + if runtime.GOOS != "linux" && testCase.isV6 { + t.Skip("Platform does not support IPv6, skipping IPv6 test...") + } else if testCase.isV6 { + v6Addr = "2001:db8::4242:4711/128" + } + peerPrivateKey, _ := wgtypes.GeneratePrivateKey() newNet, err := stdnet.NewNet() if err != nil { t.Fatal(err) } - wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun43%d", n), "100.65.65.2/24", "", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) + wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun43%d", n), "100.65.65.2/24", v6Addr, 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) require.NoError(t, err, "should create testing WGIface interface") defer wgInterface.Close() diff --git a/client/internal/routemanager/systemops_linux.go b/client/internal/routemanager/systemops_linux.go index 493f1dd5e05..9d707c6e0d1 100644 --- a/client/internal/routemanager/systemops_linux.go +++ b/client/internal/routemanager/systemops_linux.go @@ -143,7 +143,13 @@ loop: if err != nil { return nil, err } - if rt.Family != syscall.AF_INET { + + var is6 bool + if rt.Family == syscall.AF_INET { + is6 = false + } else if rt.Family == syscall.AF_INET6 { + is6 = true + } else { continue loop } @@ -156,7 +162,7 @@ loop: mask := net.CIDRMask(int(rt.DstLen), len(attr.Value)*8) cidr, _ := mask.Size() routePrefix := netip.PrefixFrom(addr, cidr) - if routePrefix.IsValid() && routePrefix.Addr().Is4() { + if routePrefix.IsValid() && ((!is6 && routePrefix.Addr().Is4()) || (is6 && routePrefix.Addr().Is6())) { prefixList = append(prefixList, routePrefix) } } diff --git a/client/internal/routemanager/systemops_nonandroid.go b/client/internal/routemanager/systemops_nonandroid.go index ec0fcc9b05e..d26d5dc133a 100644 --- a/client/internal/routemanager/systemops_nonandroid.go +++ b/client/internal/routemanager/systemops_nonandroid.go @@ -85,6 +85,14 @@ func addRouteForCurrentDefaultGateway(prefix netip.Prefix, devName string) error } func existsInRouteTable(prefix netip.Prefix) (bool, error) { + linkLocalPrefix, err := netip.ParsePrefix("fe80::/10") + if err != nil { + return false, err + } + if prefix.Addr().Is6() && linkLocalPrefix.Contains(prefix.Addr()) { + // The link local prefix is not explicitly part of the routing table, but should be considered as such. + return true, nil + } routes, err := getRoutesFromTable() if err != nil { return false, err @@ -129,5 +137,24 @@ func getExistingRIBRouteGateway(prefix netip.Prefix) (net.IP, error) { return preferredSrc, nil } + // For IPv6, the gateway may not be nil even when the requested prefix is local. + // Therefore, we need to check explicitly whether the route is a local one. + if prefix.Addr().Is6() { + addresses, err := net.InterfaceAddrs() + if err != nil { + return nil, err + } + + for _, address := range addresses { + if address.Network() != "ip+net" { + continue + } + interfaceAddrPrefix := netip.MustParsePrefix(address.String()) + if interfaceAddrPrefix.Masked() == prefix.Masked() { + return preferredSrc, nil + } + } + } + return gateway, nil } diff --git a/client/internal/routemanager/systemops_nonandroid_test.go b/client/internal/routemanager/systemops_nonandroid_test.go index 10176eb4136..53eac3676a0 100644 --- a/client/internal/routemanager/systemops_nonandroid_test.go +++ b/client/internal/routemanager/systemops_nonandroid_test.go @@ -8,6 +8,7 @@ import ( "net" "net/netip" "os" + "runtime" "strings" "testing" @@ -38,42 +39,71 @@ func TestAddRemoveRoutes(t *testing.T) { shouldRouteToWireguard: false, shouldBeRemoved: false, }, + { + name: "Should Add And Remove Route 2001:db8:1234:5678::/64", + prefix: netip.MustParsePrefix("2001:db8:1234:5678::/64"), + shouldRouteToWireguard: true, + shouldBeRemoved: true, + }, + { + name: "Should Not Add Or Remove Route ::1/128", + prefix: netip.MustParsePrefix("::1/128"), + shouldRouteToWireguard: false, + shouldBeRemoved: false, + }, } for n, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { + + v6Addr := "" + //goland:noinspection GoBoolExpressions + if runtime.GOOS != "linux" && testCase.prefix.Addr().Is6() { + t.Skip("Platform does not support IPv6, skipping IPv6 test...") + } else if testCase.prefix.Addr().Is6() { + v6Addr = "2001:db8::4242:4711/128" + } + peerPrivateKey, _ := wgtypes.GeneratePrivateKey() newNet, err := stdnet.NewNet() if err != nil { t.Fatal(err) } - wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", "", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) + wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", v6Addr, 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) require.NoError(t, err, "should create testing WGIface interface") defer wgInterface.Close() err = wgInterface.Create() require.NoError(t, err, "should create testing wireguard interface") - err = addToRouteTableIfNoExists(testCase.prefix, wgInterface.Address().IP.String(), wgInterface.Name()) + ifaceAddr := wgInterface.Address().IP.String() + if testCase.prefix.Addr().Is6() { + ifaceAddr = wgInterface.Address6().IP.String() + } + err = addToRouteTableIfNoExists(testCase.prefix, ifaceAddr, wgInterface.Name()) require.NoError(t, err, "addToRouteTableIfNoExists should not return err") prefixGateway, err := getExistingRIBRouteGateway(testCase.prefix) require.NoError(t, err, "getExistingRIBRouteGateway should not return err") if testCase.shouldRouteToWireguard { - require.Equal(t, wgInterface.Address().IP.String(), prefixGateway.String(), "route should point to wireguard interface IP") + require.Equal(t, ifaceAddr, prefixGateway.String(), "route should point to wireguard interface IP") } else { - require.NotEqual(t, wgInterface.Address().IP.String(), prefixGateway.String(), "route should point to a different interface") + require.NotEqual(t, ifaceAddr, prefixGateway.String(), "route should point to a different interface") } exists, err := existsInRouteTable(testCase.prefix) require.NoError(t, err, "existsInRouteTable should not return err") if exists && testCase.shouldRouteToWireguard { - err = removeFromRouteTableIfNonSystem(testCase.prefix, wgInterface.Address().IP.String(), wgInterface.Name()) + err = removeFromRouteTableIfNonSystem(testCase.prefix, ifaceAddr, wgInterface.Name()) require.NoError(t, err, "removeFromRouteTableIfNonSystem should not return err") prefixGateway, err = getExistingRIBRouteGateway(testCase.prefix) require.NoError(t, err, "getExistingRIBRouteGateway should not return err") - internetGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0")) + internetGatewayAddr := netip.MustParsePrefix("0.0.0.0/0") + if testCase.prefix.Addr().Is6() { + internetGatewayAddr = netip.MustParsePrefix("::/0") + } + internetGateway, err := getExistingRIBRouteGateway(internetGatewayAddr) require.NoError(t, err) if testCase.shouldBeRemoved { @@ -128,12 +158,68 @@ func TestGetExistingRIBRouteGateway(t *testing.T) { } } +func TestGetExistingRIBRouteGateway6(t *testing.T) { + //goland:noinspection GoBoolExpressions + if runtime.GOOS != "linux" { + t.Skip("Platform does not support IPv6, skipping IPv6 test...") + } + + gateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("::/0")) + if err != nil { + t.Fatal("shouldn't return error when fetching the gateway: ", err) + } + if gateway == nil { + t.Fatal("should return a gateway") + } + addresses, err := net.InterfaceAddrs() + if err != nil { + t.Fatal("shouldn't return error when fetching interface addresses: ", err) + } + + var testingIP string + var testingPrefix netip.Prefix + for _, address := range addresses { + if address.Network() != "ip+net" { + continue + } + prefix := netip.MustParsePrefix(address.String()) + if !prefix.Addr().IsLoopback() && prefix.Addr().Is6() { + testingIP = prefix.Addr().String() + testingPrefix = prefix.Masked() + break + } + } + + localIP, err := getExistingRIBRouteGateway(testingPrefix) + if err != nil { + t.Fatal("shouldn't return error: ", err) + } + if localIP == nil { + t.Fatal("should return a gateway for local network") + } + if localIP.String() == gateway.String() { + t.Fatal("local ip should not match with gateway IP") + } + if localIP.String() != testingIP { + t.Fatalf("local ip should match with testing IP: want %s got %s", testingIP, localIP.String()) + } +} + func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) { defaultGateway, err := getExistingRIBRouteGateway(netip.MustParsePrefix("0.0.0.0/0")) t.Log("defaultGateway: ", defaultGateway) if err != nil { t.Fatal("shouldn't return error when fetching the gateway: ", err) } + var defaultGateway6 net.IP + //goland:noinspection GoBoolExpressions + if runtime.GOOS == "linux" { + defaultGateway6, err = getExistingRIBRouteGateway(netip.MustParsePrefix("::/0")) + t.Log("defaultGateway6: ", defaultGateway6) + if err != nil { + t.Fatal("shouldn't return error when fetching the IPv6 gateway: ", err) + } + } testCases := []struct { name string prefix netip.Prefix @@ -168,6 +254,43 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) { preExistingPrefix: netip.MustParsePrefix("100.100.0.0/16"), shouldAddRoute: false, }, + { + name: "Should Add And Remove random Route (IPv6)", + prefix: netip.MustParsePrefix("2001:db8::abcd/128"), + shouldAddRoute: true, + }, + { + name: "Should Add Route if bigger network exists (IPv6)", + prefix: netip.MustParsePrefix("2001:db8:b14d:abcd:1234::/96"), + preExistingPrefix: netip.MustParsePrefix("2001:db8:b14d:abcd::/64"), + shouldAddRoute: true, + }, + { + name: "Should Add Route if smaller network exists (IPv6)", + prefix: netip.MustParsePrefix("2001:db8:b14d::/48"), + preExistingPrefix: netip.MustParsePrefix("2001:db8:b14d:abcd::/64"), + shouldAddRoute: true, + }, + { + name: "Should Not Add Route if same network exists (IPv6)", + prefix: netip.MustParsePrefix("2001:db8:b14d:abcd::/64"), + preExistingPrefix: netip.MustParsePrefix("2001:db8:b14d:abcd::/64"), + shouldAddRoute: false, + }, + } + if defaultGateway6 != nil { + testCases = append(testCases, []struct { + name string + prefix netip.Prefix + preExistingPrefix netip.Prefix + shouldAddRoute bool + }{ + { + name: "Should Not Add Route if overlaps with default gateway (IPv6)", + prefix: netip.MustParsePrefix(defaultGateway6.String() + "/127"), + shouldAddRoute: false, + }, + }...) } for n, testCase := range testCases { @@ -177,12 +300,19 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) { log.SetOutput(os.Stderr) }() t.Run(testCase.name, func(t *testing.T) { + v6Addr := "" + if testCase.prefix.Addr().Is6() && defaultGateway6 == nil { + t.Skip("Platform does not support IPv6, skipping IPv6 test...") + } else if testCase.prefix.Addr().Is6() { + v6Addr = "2001:db8::4242:4711/128" + } + peerPrivateKey, _ := wgtypes.GeneratePrivateKey() newNet, err := stdnet.NewNet() if err != nil { t.Fatal(err) } - wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", "", 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) + wgInterface, err := iface.NewWGIFace(fmt.Sprintf("utun53%d", n), "100.65.75.2/24", v6Addr, 33100, peerPrivateKey.String(), iface.DefaultMTU, newNet, nil) require.NoError(t, err, "should create testing WGIface interface") defer wgInterface.Close() @@ -190,6 +320,9 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) { require.NoError(t, err, "should create testing wireguard interface") MockAddr := wgInterface.Address().IP.String() + if testCase.prefix.Addr().Is6() { + MockAddr = wgInterface.Address6().IP.String() + } MockDevName := wgInterface.Name() // Prepare the environment @@ -234,9 +367,7 @@ func TestExistsInRouteTable(t *testing.T) { var addressPrefixes []netip.Prefix for _, address := range addresses { p := netip.MustParsePrefix(address.String()) - if p.Addr().Is4() { - addressPrefixes = append(addressPrefixes, p.Masked()) - } + addressPrefixes = append(addressPrefixes, p.Masked()) } for _, prefix := range addressPrefixes { From 23204c7ac6090dc3ae134107a119369e97fc891a Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Fri, 8 Mar 2024 05:47:01 +0100 Subject: [PATCH 31/42] Fix IPv6 tests running on unsupported environments --- client/firewall/iptables/router_linux_test.go | 6 ++++++ client/firewall/nftables/manager_linux_test.go | 5 +++++ client/firewall/nftables/router_linux_test.go | 13 ++++++++++++- client/internal/routemanager/manager_test.go | 2 +- client/internal/routemanager/systemops_linux.go | 7 ++++--- .../routemanager/systemops_nonandroid_test.go | 8 ++++---- client/system/info_linux.go | 3 +-- iface/iface_android.go | 4 ++++ iface/iface_darwin.go | 4 ++++ iface/iface_ios.go | 4 ++++ iface/iface_linux.go | 5 +++++ 11 files changed, 50 insertions(+), 11 deletions(-) diff --git a/client/firewall/iptables/router_linux_test.go b/client/firewall/iptables/router_linux_test.go index b4b81a38952..85dd55a0e5d 100644 --- a/client/firewall/iptables/router_linux_test.go +++ b/client/firewall/iptables/router_linux_test.go @@ -75,6 +75,9 @@ func TestIptablesManager_InsertRoutingRules(t *testing.T) { for _, testCase := range test.InsertRuleTestCases { t.Run(testCase.Name, func(t *testing.T) { + if testCase.IsV6 { + t.Skip("Environment does not support IPv6, skipping IPv6 test...") + } iptablesClient, err := iptables.NewWithProtocol(iptables.ProtocolIPv4) require.NoError(t, err, "failed to init iptables client") @@ -156,6 +159,9 @@ func TestIptablesManager_RemoveRoutingRules(t *testing.T) { for _, testCase := range test.RemoveRuleTestCases { t.Run(testCase.Name, func(t *testing.T) { + if testCase.IsV6 { + t.Skip("Environment does not support IPv6, skipping IPv6 test...") + } iptablesClient, _ := iptables.NewWithProtocol(iptables.ProtocolIPv4) manager, err := newRouterManager(context.TODO(), iptablesClient) diff --git a/client/firewall/nftables/manager_linux_test.go b/client/firewall/nftables/manager_linux_test.go index 9acc10e09f9..70ecbbe96ba 100644 --- a/client/firewall/nftables/manager_linux_test.go +++ b/client/firewall/nftables/manager_linux_test.go @@ -3,6 +3,7 @@ package nftables import ( "context" "fmt" + "github.com/netbirdio/netbird/client/firewall" "net" "net/netip" "testing" @@ -160,6 +161,10 @@ func TestNftablesManager(t *testing.T) { } func TestNftablesManager6(t *testing.T) { + + if !iface.SupportsIPv6() || !firewall.SupportsIPv6() { + t.Skip("Environment does not support IPv6, skipping IPv6 test...") + } mock := &iFaceMock{ NameFunc: func() string { return "lo" diff --git a/client/firewall/nftables/router_linux_test.go b/client/firewall/nftables/router_linux_test.go index 83facd59855..409189a3227 100644 --- a/client/firewall/nftables/router_linux_test.go +++ b/client/firewall/nftables/router_linux_test.go @@ -4,6 +4,7 @@ package nftables import ( "context" + "github.com/netbirdio/netbird/iface" "testing" "github.com/coreos/go-iptables/iptables" @@ -11,6 +12,7 @@ import ( "github.com/google/nftables/expr" "github.com/stretchr/testify/require" + fw "github.com/netbirdio/netbird/client/firewall" firewall "github.com/netbirdio/netbird/client/firewall/manager" "github.com/netbirdio/netbird/client/firewall/test" ) @@ -38,6 +40,9 @@ func TestNftablesManager_InsertRoutingRules(t *testing.T) { for _, testCase := range test.InsertRuleTestCases { t.Run(testCase.Name, func(t *testing.T) { + if testCase.IsV6 && table6 == nil { + t.Skip("Environment does not support IPv6, skipping IPv6 test...") + } manager, err := newRouter(context.TODO(), table, table6) require.NoError(t, err, "failed to create router") @@ -145,6 +150,9 @@ func TestNftablesManager_RemoveRoutingRules(t *testing.T) { for _, testCase := range test.RemoveRuleTestCases { t.Run(testCase.Name, func(t *testing.T) { + if testCase.IsV6 && table6 == nil { + t.Skip("Environment does not support IPv6, skipping IPv6 test...") + } manager, err := newRouter(context.TODO(), table, table6) require.NoError(t, err, "failed to create router") @@ -273,7 +281,10 @@ func createWorkTables() (*nftables.Table, *nftables.Table, error) { } table := sConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv4}) - table6 := sConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv6}) + var table6 *nftables.Table + if iface.SupportsIPv6() && fw.SupportsIPv6() { + table6 = sConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv6}) + } err = sConn.Flush() return table, table6, err diff --git a/client/internal/routemanager/manager_test.go b/client/internal/routemanager/manager_test.go index 7bbcd7593ba..c49b715c23a 100644 --- a/client/internal/routemanager/manager_test.go +++ b/client/internal/routemanager/manager_test.go @@ -823,7 +823,7 @@ func TestManagerUpdateRoutes(t *testing.T) { v6Addr := "" //goland:noinspection GoBoolExpressions - if runtime.GOOS != "linux" && testCase.isV6 { + if !iface.SupportsIPv6() && testCase.isV6 { t.Skip("Platform does not support IPv6, skipping IPv6 test...") } else if testCase.isV6 { v6Addr = "2001:db8::4242:4711/128" diff --git a/client/internal/routemanager/systemops_linux.go b/client/internal/routemanager/systemops_linux.go index 9d707c6e0d1..6e941f6b58d 100644 --- a/client/internal/routemanager/systemops_linux.go +++ b/client/internal/routemanager/systemops_linux.go @@ -145,11 +145,12 @@ loop: } var is6 bool - if rt.Family == syscall.AF_INET { + switch rt.Family { + case syscall.AF_INET: is6 = false - } else if rt.Family == syscall.AF_INET6 { + case syscall.AF_INET6: is6 = true - } else { + default: continue loop } diff --git a/client/internal/routemanager/systemops_nonandroid_test.go b/client/internal/routemanager/systemops_nonandroid_test.go index 53eac3676a0..6afb2992ca3 100644 --- a/client/internal/routemanager/systemops_nonandroid_test.go +++ b/client/internal/routemanager/systemops_nonandroid_test.go @@ -5,10 +5,10 @@ package routemanager import ( "bytes" "fmt" + "github.com/netbirdio/netbird/client/firewall" "net" "net/netip" "os" - "runtime" "strings" "testing" @@ -58,7 +58,7 @@ func TestAddRemoveRoutes(t *testing.T) { v6Addr := "" //goland:noinspection GoBoolExpressions - if runtime.GOOS != "linux" && testCase.prefix.Addr().Is6() { + if (!iface.SupportsIPv6() || !firewall.SupportsIPv6()) && testCase.prefix.Addr().Is6() { t.Skip("Platform does not support IPv6, skipping IPv6 test...") } else if testCase.prefix.Addr().Is6() { v6Addr = "2001:db8::4242:4711/128" @@ -160,7 +160,7 @@ func TestGetExistingRIBRouteGateway(t *testing.T) { func TestGetExistingRIBRouteGateway6(t *testing.T) { //goland:noinspection GoBoolExpressions - if runtime.GOOS != "linux" { + if !iface.SupportsIPv6() { t.Skip("Platform does not support IPv6, skipping IPv6 test...") } @@ -213,7 +213,7 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) { } var defaultGateway6 net.IP //goland:noinspection GoBoolExpressions - if runtime.GOOS == "linux" { + if iface.SupportsIPv6() && firewall.SupportsIPv6() { defaultGateway6, err = getExistingRIBRouteGateway(netip.MustParsePrefix("::/0")) t.Log("defaultGateway6: ", defaultGateway6) if err != nil { diff --git a/client/system/info_linux.go b/client/system/info_linux.go index 48591f57117..82a4e9312e6 100644 --- a/client/system/info_linux.go +++ b/client/system/info_linux.go @@ -8,7 +8,6 @@ import ( "context" "github.com/netbirdio/netbird/client/firewall" "github.com/netbirdio/netbird/iface" - "github.com/netbirdio/netbird/iface/netstack" "os" "os/exec" "runtime" @@ -129,5 +128,5 @@ func sysInfo() (serialNumber string, productName string, manufacturer string) { func _checkIPv6Support() bool { return firewall.SupportsIPv6() && - iface.WireGuardModuleIsLoaded() && !netstack.IsEnabled() + iface.SupportsIPv6() } diff --git a/iface/iface_android.go b/iface/iface_android.go index ff39ad65ca4..d819698cc5f 100644 --- a/iface/iface_android.go +++ b/iface/iface_android.go @@ -43,3 +43,7 @@ func (w *WGIface) CreateOnAndroid(routes []string, dns string, searchDomains []s func (w *WGIface) Create() error { return fmt.Errorf("this function has not implemented on this platform") } + +func SupportsIPv6() bool { + return false +} diff --git a/iface/iface_darwin.go b/iface/iface_darwin.go index 0fc2c6deabe..6736467504d 100644 --- a/iface/iface_darwin.go +++ b/iface/iface_darwin.go @@ -41,3 +41,7 @@ func NewWGIFace(iFaceName string, address string, address6 string, wgPort int, w func (w *WGIface) CreateOnAndroid([]string, string, []string) error { return fmt.Errorf("this function has not implemented on this platform") } + +func SupportsIPv6() bool { + return false +} diff --git a/iface/iface_ios.go b/iface/iface_ios.go index 94c3b488cac..f6ed2fa1528 100644 --- a/iface/iface_ios.go +++ b/iface/iface_ios.go @@ -32,3 +32,7 @@ func NewWGIFace(iFaceName string, address string, address6 string, wgPort int, w func (w *WGIface) CreateOnAndroid([]string, string, []string) error { return fmt.Errorf("this function has not implemented on this platform") } + +func SupportsIPv6() bool { + return false +} diff --git a/iface/iface_linux.go b/iface/iface_linux.go index ae7d1553871..59e62ea938c 100644 --- a/iface/iface_linux.go +++ b/iface/iface_linux.go @@ -6,6 +6,7 @@ package iface import ( "fmt" log "github.com/sirupsen/logrus" + "golang.org/x/net/nettest" "github.com/pion/transport/v3" @@ -59,3 +60,7 @@ func NewWGIFace(iFaceName string, address string, address6 string, wgPort int, w func (w *WGIface) CreateOnAndroid([]string, string, []string) error { return fmt.Errorf("this function has not implemented on this platform") } + +func SupportsIPv6() bool { + return nettest.SupportsIPv6() && WireGuardModuleIsLoaded() && !netstack.IsEnabled() +} From 541740b5d03246f7bd1c3f8a5c95702952554450 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Fri, 8 Mar 2024 05:51:41 +0100 Subject: [PATCH 32/42] Fix import cycle in tests --- client/firewall/nftables/manager_linux_test.go | 3 +-- client/firewall/nftables/router_linux_test.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/client/firewall/nftables/manager_linux_test.go b/client/firewall/nftables/manager_linux_test.go index 70ecbbe96ba..9029d979a1a 100644 --- a/client/firewall/nftables/manager_linux_test.go +++ b/client/firewall/nftables/manager_linux_test.go @@ -3,7 +3,6 @@ package nftables import ( "context" "fmt" - "github.com/netbirdio/netbird/client/firewall" "net" "net/netip" "testing" @@ -162,7 +161,7 @@ func TestNftablesManager(t *testing.T) { func TestNftablesManager6(t *testing.T) { - if !iface.SupportsIPv6() || !firewall.SupportsIPv6() { + if !iface.SupportsIPv6() { t.Skip("Environment does not support IPv6, skipping IPv6 test...") } mock := &iFaceMock{ diff --git a/client/firewall/nftables/router_linux_test.go b/client/firewall/nftables/router_linux_test.go index 409189a3227..23db1f74ce6 100644 --- a/client/firewall/nftables/router_linux_test.go +++ b/client/firewall/nftables/router_linux_test.go @@ -12,7 +12,6 @@ import ( "github.com/google/nftables/expr" "github.com/stretchr/testify/require" - fw "github.com/netbirdio/netbird/client/firewall" firewall "github.com/netbirdio/netbird/client/firewall/manager" "github.com/netbirdio/netbird/client/firewall/test" ) @@ -282,7 +281,7 @@ func createWorkTables() (*nftables.Table, *nftables.Table, error) { table := sConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv4}) var table6 *nftables.Table - if iface.SupportsIPv6() && fw.SupportsIPv6() { + if iface.SupportsIPv6() { table6 = sConn.AddTable(&nftables.Table{Name: tableName, Family: nftables.TableFamilyIPv6}) } err = sConn.Flush() From 162f29a7576d4bbe7971180c72cea9fd68d7b8cc Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Fri, 8 Mar 2024 05:58:58 +0100 Subject: [PATCH 33/42] Add missing method SupportsIPv6() for windows --- iface/iface_windows.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iface/iface_windows.go b/iface/iface_windows.go index dc58ace35a1..8b9a1867cad 100644 --- a/iface/iface_windows.go +++ b/iface/iface_windows.go @@ -42,3 +42,7 @@ func (w *WGIface) CreateOnAndroid([]string, string, []string) error { func (w *WGIface) GetInterfaceGUIDString() (string, error) { return w.tun.(*tunDevice).getInterfaceGUIDString() } + +func SupportsIPv6() bool { + return false +} From c088380361d1e3691281f55b376bb7bdc618974e Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Fri, 8 Mar 2024 06:24:16 +0100 Subject: [PATCH 34/42] Require IPv6 default route for IPv6 tests --- .../routemanager/systemops_nonandroid_test.go | 23 ++++++++++++++++--- iface/iface_linux.go | 6 ++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/client/internal/routemanager/systemops_nonandroid_test.go b/client/internal/routemanager/systemops_nonandroid_test.go index 6afb2992ca3..ce04a782b77 100644 --- a/client/internal/routemanager/systemops_nonandroid_test.go +++ b/client/internal/routemanager/systemops_nonandroid_test.go @@ -5,6 +5,7 @@ package routemanager import ( "bytes" "fmt" + "github.com/google/gopacket/routing" "github.com/netbirdio/netbird/client/firewall" "net" "net/netip" @@ -57,8 +58,9 @@ func TestAddRemoveRoutes(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { v6Addr := "" + hasV6DefaultRoute, err := EnvironmentHasIPv6DefaultRoute() //goland:noinspection GoBoolExpressions - if (!iface.SupportsIPv6() || !firewall.SupportsIPv6()) && testCase.prefix.Addr().Is6() { + if (!iface.SupportsIPv6() || !firewall.SupportsIPv6() || !hasV6DefaultRoute || err != nil) && testCase.prefix.Addr().Is6() { t.Skip("Platform does not support IPv6, skipping IPv6 test...") } else if testCase.prefix.Addr().Is6() { v6Addr = "2001:db8::4242:4711/128" @@ -160,7 +162,9 @@ func TestGetExistingRIBRouteGateway(t *testing.T) { func TestGetExistingRIBRouteGateway6(t *testing.T) { //goland:noinspection GoBoolExpressions - if !iface.SupportsIPv6() { + hasV6DefaultRoute, err := EnvironmentHasIPv6DefaultRoute() + //goland:noinspection GoBoolExpressions + if !iface.SupportsIPv6() || !firewall.SupportsIPv6() || !hasV6DefaultRoute || err != nil { t.Skip("Platform does not support IPv6, skipping IPv6 test...") } @@ -212,8 +216,9 @@ func TestAddExistAndRemoveRouteNonAndroid(t *testing.T) { t.Fatal("shouldn't return error when fetching the gateway: ", err) } var defaultGateway6 net.IP + hasV6DefaultRoute, err := EnvironmentHasIPv6DefaultRoute() //goland:noinspection GoBoolExpressions - if iface.SupportsIPv6() && firewall.SupportsIPv6() { + if iface.SupportsIPv6() && firewall.SupportsIPv6() && hasV6DefaultRoute && err == nil { defaultGateway6, err = getExistingRIBRouteGateway(netip.MustParsePrefix("::/0")) t.Log("defaultGateway6: ", defaultGateway6) if err != nil { @@ -418,3 +423,15 @@ func TestIsSubRange(t *testing.T) { } } } + +func EnvironmentHasIPv6DefaultRoute() (bool, error) { + router, err := routing.New() + if err != nil { + return false, nil + } + routeIface, _, _, err := router.Route(netip.MustParsePrefix("::/0").Addr().AsSlice()) + if err != nil { + return false, err + } + return routeIface != nil, nil +} diff --git a/iface/iface_linux.go b/iface/iface_linux.go index 59e62ea938c..26d9eaaee9a 100644 --- a/iface/iface_linux.go +++ b/iface/iface_linux.go @@ -5,12 +5,10 @@ package iface import ( "fmt" + "github.com/netbirdio/netbird/iface/netstack" + "github.com/pion/transport/v3" log "github.com/sirupsen/logrus" "golang.org/x/net/nettest" - - "github.com/pion/transport/v3" - - "github.com/netbirdio/netbird/iface/netstack" ) // NewWGIFace Creates a new WireGuard interface instance From dd74e6500a7d6e25da95aa1f9c26b65932351152 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Fri, 8 Mar 2024 06:39:37 +0100 Subject: [PATCH 35/42] Fix panics in routemanager tests on non-linux --- .../internal/routemanager/systemops_nonandroid_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/internal/routemanager/systemops_nonandroid_test.go b/client/internal/routemanager/systemops_nonandroid_test.go index ce04a782b77..8ef6d9d19fd 100644 --- a/client/internal/routemanager/systemops_nonandroid_test.go +++ b/client/internal/routemanager/systemops_nonandroid_test.go @@ -10,6 +10,7 @@ import ( "net" "net/netip" "os" + "runtime" "strings" "testing" @@ -425,9 +426,15 @@ func TestIsSubRange(t *testing.T) { } func EnvironmentHasIPv6DefaultRoute() (bool, error) { + //goland:noinspection GoBoolExpressions + if runtime.GOOS != "linux" { + // TODO when implementing IPv6 for other operating systems, this should be replaced with code that determines + // whether a default route for IPv6 exists (routing.Router panics on non-linux). + return false, nil + } router, err := routing.New() if err != nil { - return false, nil + return false, err } routeIface, _, _, err := router.Route(netip.MustParsePrefix("::/0").Addr().AsSlice()) if err != nil { From 807fceacb2abc0797ec2b68f1f1e512e3e8483ad Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Fri, 8 Mar 2024 07:00:23 +0100 Subject: [PATCH 36/42] Fix some more route manager tests concerning IPv6 --- .../routemanager/systemops_nonandroid_test.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/client/internal/routemanager/systemops_nonandroid_test.go b/client/internal/routemanager/systemops_nonandroid_test.go index 8ef6d9d19fd..4cf3c58a31d 100644 --- a/client/internal/routemanager/systemops_nonandroid_test.go +++ b/client/internal/routemanager/systemops_nonandroid_test.go @@ -370,10 +370,15 @@ func TestExistsInRouteTable(t *testing.T) { t.Fatal("shouldn't return error when fetching interface addresses: ", err) } + hasV6DefaultRoute, err := EnvironmentHasIPv6DefaultRoute() + shouldIncludeV6Routes := iface.SupportsIPv6() && firewall.SupportsIPv6() && hasV6DefaultRoute && err == nil + var addressPrefixes []netip.Prefix for _, address := range addresses { p := netip.MustParsePrefix(address.String()) - addressPrefixes = append(addressPrefixes, p.Masked()) + if p.Addr().Is4() || shouldIncludeV6Routes { + addressPrefixes = append(addressPrefixes, p.Masked()) + } } for _, prefix := range addressPrefixes { @@ -388,6 +393,9 @@ func TestExistsInRouteTable(t *testing.T) { } func TestIsSubRange(t *testing.T) { + hasV6DefaultRoute, err := EnvironmentHasIPv6DefaultRoute() + shouldIncludeV6Routes := iface.SupportsIPv6() && firewall.SupportsIPv6() && hasV6DefaultRoute && err == nil + addresses, err := net.InterfaceAddrs() if err != nil { t.Fatal("shouldn't return error when fetching interface addresses: ", err) @@ -397,7 +405,7 @@ func TestIsSubRange(t *testing.T) { var nonSubRangeAddressPrefixes []netip.Prefix for _, address := range addresses { p := netip.MustParsePrefix(address.String()) - if !p.Addr().IsLoopback() && p.Addr().Is4() && p.Bits() < 32 { + if !p.Addr().IsLoopback() && (p.Addr().Is4() && p.Bits() < 32) || (p.Addr().Is6() && shouldIncludeV6Routes && p.Bits() < 128) { p2 := netip.PrefixFrom(p.Masked().Addr(), p.Bits()+1) subRangeAddressPrefixes = append(subRangeAddressPrefixes, p2) nonSubRangeAddressPrefixes = append(nonSubRangeAddressPrefixes, p.Masked()) From 160b1fe4f175a9c820b7a1140a4ac14b1a00b645 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Sun, 10 Mar 2024 02:22:14 +0100 Subject: [PATCH 37/42] Add some final client-side tests --- .../firewall/nftables/manager_linux_test.go | 240 ++++++++++++++ iface/iface_test.go | 308 ++++++++++++++++++ 2 files changed, 548 insertions(+) diff --git a/client/firewall/nftables/manager_linux_test.go b/client/firewall/nftables/manager_linux_test.go index 9029d979a1a..ebf4570b4b1 100644 --- a/client/firewall/nftables/manager_linux_test.go +++ b/client/firewall/nftables/manager_linux_test.go @@ -159,6 +159,62 @@ func TestNftablesManager(t *testing.T) { require.NoError(t, err, "failed to reset") } +func TestNftablesManager6Disabled(t *testing.T) { + mock := &iFaceMock{ + NameFunc: func() string { + return "lo" + }, + AddressFunc: func() iface.WGAddress { + return iface.WGAddress{ + IP: net.ParseIP("100.96.0.1"), + Network: &net.IPNet{ + IP: net.ParseIP("100.96.0.0"), + Mask: net.IPv4Mask(255, 255, 255, 0), + }, + } + }, + Address6Func: func() *iface.WGAddress { return nil }, + } + + // just check on the local interface + manager, err := Create(context.Background(), mock) + require.NoError(t, err) + time.Sleep(time.Second * 3) + + defer func() { + err = manager.Reset() + require.NoError(t, err, "failed to reset") + time.Sleep(time.Second) + }() + + ip := net.ParseIP("2001:db8::fedc:ba09:8765:4321") + + testClient := &nftables.Conn{} + + _, err = manager.AddFiltering( + ip, + fw.ProtocolTCP, + nil, + &fw.Port{Values: []int{53}}, + fw.RuleDirectionIN, + fw.ActionDrop, + "", + "", + ) + require.Error(t, err, "IPv6 rule should not be added when IPv6 is disabled") + + err = manager.Flush() + require.NoError(t, err, "failed to flush") + + rules, err := testClient.GetRules(manager.aclManager.workTable, manager.aclManager.chainInputRules) + require.NoError(t, err, "failed to get rules") + + require.Len(t, rules, 0, "expected no rules") + + err = manager.Reset() + require.NoError(t, err, "failed to reset") +} + func TestNftablesManager6(t *testing.T) { if !iface.SupportsIPv6() { @@ -199,6 +255,8 @@ func TestNftablesManager6(t *testing.T) { time.Sleep(time.Second) }() + require.True(t, manager.V6Active(), "IPv6 is not active even though it should be.") + ip := net.ParseIP("2001:db8::fedc:ba09:8765:4321") testClient := &nftables.Conn{} @@ -283,6 +341,188 @@ func TestNftablesManager6(t *testing.T) { require.NoError(t, err, "failed to reset") } +func TestNftablesManagerAddressReset6(t *testing.T) { + + if !iface.SupportsIPv6() { + t.Skip("Environment does not support IPv6, skipping IPv6 test...") + } + mock := &iFaceMock{ + NameFunc: func() string { + return "lo" + }, + AddressFunc: func() iface.WGAddress { + return iface.WGAddress{ + IP: net.ParseIP("100.96.0.1"), + Network: &net.IPNet{ + IP: net.ParseIP("100.96.0.0"), + Mask: net.IPv4Mask(255, 255, 255, 0), + }, + } + }, + Address6Func: func() *iface.WGAddress { + return &iface.WGAddress{ + IP: net.ParseIP("2001:db8::0123:4567:890a:bcde"), + Network: &net.IPNet{ + IP: net.ParseIP("2001:db8::"), + Mask: net.CIDRMask(64, 128), + }, + } + }, + } + + // just check on the local interface + manager, err := Create(context.Background(), mock) + require.NoError(t, err) + time.Sleep(time.Second * 3) + + defer func() { + err = manager.Reset() + require.NoError(t, err, "failed to reset") + time.Sleep(time.Second) + }() + + require.True(t, manager.V6Active(), "IPv6 is not active even though it should be.") + + ip := net.ParseIP("2001:db8::fedc:ba09:8765:4321") + + testClient := &nftables.Conn{} + + rule, err := manager.AddFiltering( + ip, + fw.ProtocolTCP, + nil, + &fw.Port{Values: []int{53}}, + fw.RuleDirectionIN, + fw.ActionDrop, + "", + "", + ) + require.NoError(t, err, "failed to add rule") + + err = manager.Flush() + require.NoError(t, err, "failed to flush") + + rules, err := testClient.GetRules(manager.aclManager.workTable6, manager.aclManager.chainInputRules6) + require.NoError(t, err, "failed to get rules") + + require.Len(t, rules, 1, "expected 1 rules") + + mock.Address6Func = func() *iface.WGAddress { + return nil + } + + err = manager.ResetV6Firewall() + require.NoError(t, err, "failed to reset IPv6 firewall") + + err = manager.Flush() + require.NoError(t, err, "failed to flush") + + require.False(t, manager.V6Active(), "IPv6 is active even though it shouldn't be.") + + tables, err := testClient.ListTablesOfFamily(nftables.TableFamilyIPv6) + require.NoError(t, err, "failed to list IPv6 tables") + + for _, table := range tables { + if table.Name == tableName { + t.Errorf("When IPv6 is disabled, the netbird table should not exist.") + } + } + + mock.Address6Func = func() *iface.WGAddress { + return &iface.WGAddress{ + IP: net.ParseIP("2001:db8::0123:4567:890a:bcdf"), + Network: &net.IPNet{ + IP: net.ParseIP("2001:db8::"), + Mask: net.CIDRMask(64, 128), + }, + } + } + + err = manager.ResetV6Firewall() + require.NoError(t, err, "failed to reset IPv6 firewall") + + require.True(t, manager.V6Active(), "IPv6 is not active even though it should be.") + + rule, err = manager.AddFiltering( + ip, + fw.ProtocolTCP, + nil, + &fw.Port{Values: []int{53}}, + fw.RuleDirectionIN, + fw.ActionDrop, + "", + "", + ) + require.NoError(t, err, "failed to add rule") + + err = manager.Flush() + require.NoError(t, err, "failed to flush") + + rules, err = testClient.GetRules(manager.aclManager.workTable6, manager.aclManager.chainInputRules6) + require.NoError(t, err, "failed to get rules") + + require.Len(t, rules, 1, "expected 1 rule") + + ipToAdd, _ := netip.AddrFromSlice(ip) + add := ipToAdd.Unmap() + expectedExprs := []expr.Any{ + &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: ifname("lo"), + }, + &expr.Meta{ + Key: expr.MetaKeyL4PROTO, + Register: 1, + }, + &expr.Cmp{ + Register: 1, + Op: expr.CmpOpEq, + Data: []byte{unix.IPPROTO_TCP}, + }, + &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseNetworkHeader, + Offset: 8, + Len: 16, + }, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: add.AsSlice(), + }, + &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseTransportHeader, + Offset: 2, + Len: 2, + }, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: []byte{0, 53}, + }, + &expr.Verdict{Kind: expr.VerdictDrop}, + } + require.ElementsMatch(t, rules[0].Exprs, expectedExprs, "expected the same expressions") + + for _, r := range rule { + err = manager.DeleteRule(r) + require.NoError(t, err, "failed to delete rule") + } + + err = manager.Flush() + require.NoError(t, err, "failed to flush") + + rules, err = testClient.GetRules(manager.aclManager.workTable6, manager.aclManager.chainInputRules6) + require.NoError(t, err, "failed to get rules") + require.Len(t, rules, 0, "expected 0 rules after deletion") + + err = manager.Reset() + require.NoError(t, err, "failed to reset") +} + func TestNFtablesCreatePerformance(t *testing.T) { mock := &iFaceMock{ NameFunc: func() string { diff --git a/iface/iface_test.go b/iface/iface_test.go index ddb0585e557..75f995bbff4 100644 --- a/iface/iface_test.go +++ b/iface/iface_test.go @@ -83,6 +83,64 @@ func TestWGIface_UpdateAddr(t *testing.T) { } +func TestWGIface_UpdateAddr6(t *testing.T) { + if !SupportsIPv6() { + t.Skip("Environment does not support IPv6, skipping IPv6 test...") + } + + ifaceName := fmt.Sprintf("utun%d", WgIntNumber+4) + + addr := "100.64.0.1/8" + addr6 := "2001:db8:1234:abcd::42/64" + wgPort := 33100 + newNet, err := stdnet.NewNet() + if err != nil { + t.Fatal(err) + } + + iface, err := NewWGIFace(ifaceName, addr, addr6, wgPort, key, DefaultMTU, newNet, nil) + if err != nil { + t.Fatal(err) + } + err = iface.Create() + if err != nil { + t.Fatal(err) + } + defer func() { + err = iface.Close() + if err != nil { + t.Error(err) + } + + }() + + _, err = iface.Up() + if err != nil { + t.Fatal(err) + } + + addrs, err := getIfaceAddrs(ifaceName) + if err != nil { + t.Error(err) + } + assert.Equal(t, addr, addrs[0].String()) + + //update WireGuard address + addr = "100.64.0.2/8" + err = iface.UpdateAddr(addr) + if err != nil { + t.Fatal(err) + } + + addrs, err = getIfaceAddrs(ifaceName) + if err != nil { + t.Error(err) + } + + assert.Equal(t, addr6, addrs[1].String()) + +} + func getIfaceAddrs(ifaceName string) ([]net.Addr, error) { ief, err := net.InterfaceByName(ifaceName) if err != nil { @@ -128,6 +186,44 @@ func Test_CreateInterface(t *testing.T) { }() } +func Test_CreateInterface6(t *testing.T) { + if !SupportsIPv6() { + t.Skip("Environment does not support IPv6, skipping IPv6 test...") + } + + ifaceName := fmt.Sprintf("utun%d", WgIntNumber+1) + wgIP := "10.99.99.1/32" + wgIP6 := "2001:db8:1234:abcd::43/64" + newNet, err := stdnet.NewNet() + if err != nil { + t.Fatal(err) + } + iface, err := NewWGIFace(ifaceName, wgIP, wgIP6, 33100, key, DefaultMTU, newNet, nil) + if err != nil { + t.Fatal(err) + } + err = iface.Create() + if err != nil { + t.Fatal(err) + } + defer func() { + err = iface.Close() + if err != nil { + t.Error(err) + } + }() + wg, err := wgctrl.New() + if err != nil { + t.Fatal(err) + } + defer func() { + err = wg.Close() + if err != nil { + t.Error(err) + } + }() +} + func Test_Close(t *testing.T) { ifaceName := fmt.Sprintf("utun%d", WgIntNumber+2) wgIP := "10.99.99.2/32" @@ -162,6 +258,45 @@ func Test_Close(t *testing.T) { } } +func Test_Close6(t *testing.T) { + if !SupportsIPv6() { + t.Skip("Environment does not support IPv6, skipping IPv6 test...") + } + + ifaceName := fmt.Sprintf("utun%d", WgIntNumber+2) + wgIP := "10.99.99.2/32" + wgIP6 := "2001:db8:1234:abcd::44/64" + wgPort := 33100 + newNet, err := stdnet.NewNet() + if err != nil { + t.Fatal(err) + } + + iface, err := NewWGIFace(ifaceName, wgIP, wgIP6, wgPort, key, DefaultMTU, newNet, nil) + if err != nil { + t.Fatal(err) + } + err = iface.Create() + if err != nil { + t.Fatal(err) + } + wg, err := wgctrl.New() + if err != nil { + t.Fatal(err) + } + defer func() { + err = wg.Close() + if err != nil { + t.Error(err) + } + }() + + err = iface.Close() + if err != nil { + t.Fatal(err) + } +} + func Test_ConfigureInterface(t *testing.T) { ifaceName := fmt.Sprintf("utun%d", WgIntNumber+3) wgIP := "10.99.99.5/30" @@ -271,6 +406,73 @@ func Test_UpdatePeer(t *testing.T) { } } +func Test_UpdatePeer6(t *testing.T) { + if !SupportsIPv6() { + t.Skip("Environment does not support IPv6, skipping IPv6 test...") + } + + ifaceName := fmt.Sprintf("utun%d", WgIntNumber+4) + wgIP := "10.99.99.9/30" + wgIP6 := "2001:db8:1234:abcd::45/64" + newNet, err := stdnet.NewNet() + if err != nil { + t.Fatal(err) + } + + iface, err := NewWGIFace(ifaceName, wgIP, wgIP6, 33100, key, DefaultMTU, newNet, nil) + if err != nil { + t.Fatal(err) + } + err = iface.Create() + if err != nil { + t.Fatal(err) + } + defer func() { + err = iface.Close() + if err != nil { + t.Error(err) + } + }() + + _, err = iface.Up() + if err != nil { + t.Fatal(err) + } + keepAlive := 15 * time.Second + allowedIP := "10.99.99.10/32" + allowedIP6 := "2001:db8:1234:abcd::46/128" + endpoint, err := net.ResolveUDPAddr("udp", "127.0.0.1:9900") + if err != nil { + t.Fatal(err) + } + err = iface.UpdatePeer(peerPubKey, allowedIP+","+allowedIP6, keepAlive, endpoint, nil) + if err != nil { + t.Fatal(err) + } + peer, err := getPeer(ifaceName, peerPubKey) + if err != nil { + t.Fatal(err) + } + if peer.PersistentKeepaliveInterval != keepAlive { + t.Fatal("configured peer with mismatched keepalive interval value") + } + + if peer.Endpoint.String() != endpoint.String() { + t.Fatal("configured peer with mismatched endpoint") + } + + var foundAllowedIP bool + for _, aip := range peer.AllowedIPs { + if aip.String() == allowedIP6 { + foundAllowedIP = true + break + } + } + if !foundAllowedIP { + t.Fatal("configured peer with mismatched Allowed IPs") + } +} + func Test_RemovePeer(t *testing.T) { ifaceName := fmt.Sprintf("utun%d", WgIntNumber+4) wgIP := "10.99.99.13/30" @@ -416,6 +618,112 @@ func Test_ConnectPeers(t *testing.T) { } +func Test_ConnectPeers6(t *testing.T) { + if !SupportsIPv6() { + t.Skip("Environment does not support IPv6, skipping IPv6 test...") + } + + peer1ifaceName := fmt.Sprintf("utun%d", WgIntNumber+400) + peer1wgIP := "10.99.99.17/30" + peer1wgIP6 := "2001:db8:1234:abcd::47/64" + peer1Key, _ := wgtypes.GeneratePrivateKey() + peer1wgPort := 33100 + + peer2ifaceName := "utun500" + peer2wgIP := "10.99.99.18/30" + peer2wgIP6 := "2001:db8:1234:abcd::48/64" + peer2Key, _ := wgtypes.GeneratePrivateKey() + peer2wgPort := 33200 + + keepAlive := 1 * time.Second + newNet, err := stdnet.NewNet() + if err != nil { + t.Fatal(err) + } + + iface1, err := NewWGIFace(peer1ifaceName, peer1wgIP, peer1wgIP6, peer1wgPort, peer1Key.String(), DefaultMTU, newNet, nil) + if err != nil { + t.Fatal(err) + } + err = iface1.Create() + if err != nil { + t.Fatal(err) + } + + _, err = iface1.Up() + if err != nil { + t.Fatal(err) + } + + peer1endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", peer1wgPort)) + if err != nil { + t.Fatal(err) + } + + newNet, err = stdnet.NewNet() + if err != nil { + t.Fatal(err) + } + iface2, err := NewWGIFace(peer2ifaceName, peer2wgIP, peer2wgIP6, peer2wgPort, peer2Key.String(), DefaultMTU, newNet, nil) + if err != nil { + t.Fatal(err) + } + err = iface2.Create() + if err != nil { + t.Fatal(err) + } + + _, err = iface2.Up() + if err != nil { + t.Fatal(err) + } + + peer2endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", peer2wgPort)) + if err != nil { + t.Fatal(err) + } + defer func() { + err = iface1.Close() + if err != nil { + t.Error(err) + } + err = iface2.Close() + if err != nil { + t.Error(err) + } + }() + + err = iface1.UpdatePeer(peer2Key.PublicKey().String(), peer2wgIP, keepAlive, peer2endpoint, nil) + if err != nil { + t.Fatal(err) + } + err = iface2.UpdatePeer(peer1Key.PublicKey().String(), peer1wgIP, keepAlive, peer1endpoint, nil) + if err != nil { + t.Fatal(err) + } + // todo: investigate why in some tests execution we need 30s + timeout := 30 * time.Second + timeoutChannel := time.After(timeout) + + for { + select { + case <-timeoutChannel: + t.Fatalf("waiting for peer handshake timeout after %s", timeout.String()) + default: + } + + peer, gpErr := getPeer(peer1ifaceName, peer2Key.PublicKey().String()) + if gpErr != nil { + t.Fatal(gpErr) + } + if !peer.LastHandshakeTime.IsZero() { + t.Log("peers successfully handshake") + break + } + } + +} + func getPeer(ifaceName, peerPubKey string) (wgtypes.Peer, error) { wg, err := wgctrl.New() if err != nil { From e2d6dffec6994d317e526fa4cf9f6aaa855350ac Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Mon, 11 Mar 2024 02:41:17 +0100 Subject: [PATCH 38/42] Add IPv6 tests for management code, small fixes --- .../routemanager/systemops_nonandroid_test.go | 2 + management/server/account_test.go | 238 ++++++++++---- management/server/activity/codes.go | 2 - management/server/dns_test.go | 54 ++-- management/server/group.go | 75 ++++- management/server/group_test.go | 153 ++++++++- management/server/http/groups_handler.go | 8 + management/server/network_test.go | 39 ++- management/server/peer.go | 35 +- management/server/peer_test.go | 103 ++++++ management/server/policy_test.go | 6 + management/server/route.go | 14 +- management/server/route_test.go | 300 +++++++++++++++--- 13 files changed, 851 insertions(+), 178 deletions(-) diff --git a/client/internal/routemanager/systemops_nonandroid_test.go b/client/internal/routemanager/systemops_nonandroid_test.go index 4cf3c58a31d..ac3df30c822 100644 --- a/client/internal/routemanager/systemops_nonandroid_test.go +++ b/client/internal/routemanager/systemops_nonandroid_test.go @@ -393,6 +393,8 @@ func TestExistsInRouteTable(t *testing.T) { } func TestIsSubRange(t *testing.T) { + // Note: This test may fail for IPv6 in some environments, where there actually exists another route that the + // determined prefix is a sub-range of. hasV6DefaultRoute, err := EnvironmentHasIPv6DefaultRoute() shouldIncludeV6Routes := iface.SupportsIPv6() && firewall.SupportsIPv6() && hasV6DefaultRoute && err == nil diff --git a/management/server/account_test.go b/management/server/account_test.go index bf7f42e107a..395cf069490 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -924,78 +924,136 @@ func TestAccountManager_DeleteAccount(t *testing.T) { } func TestAccountManager_AddPeer(t *testing.T) { - manager, err := createManager(t) - if err != nil { - t.Fatal(err) - return - } - userID := "testingUser" - account, err := createAccount(manager, "test_account", userID, "netbird.cloud") - if err != nil { - t.Fatal(err) + testCases := []struct { + name string + peerIPv6Supported bool + allGroupIPv6Enabled bool + }{ + { + name: "Peer and Group IPv6 enabled", + peerIPv6Supported: true, + allGroupIPv6Enabled: true, + }, + { + name: "Peer IPv6 enabled, Group IPv6 disabled", + peerIPv6Supported: true, + allGroupIPv6Enabled: false, + }, + { + name: "Peer IPv6 disabled, Group IPv6 enabled", + peerIPv6Supported: false, + allGroupIPv6Enabled: true, + }, + { + name: "Peer and Group IPv6 disabled", + peerIPv6Supported: false, + allGroupIPv6Enabled: false, + }, } - serial := account.Network.CurrentSerial() // should be 0 + for _, c := range testCases { + t.Run(c.name, func(t *testing.T) { + manager, err := createManager(t) + if err != nil { + t.Fatal(err) + return + } - setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID, false) - if err != nil { - t.Fatal("error creating setup key") - return - } + userID := "testingUser" + account, err := createAccount(manager, "test_account", userID, "netbird.cloud") + if err != nil { + t.Fatal(err) + } - if account.Network.Serial != 0 { - t.Errorf("expecting account network to have an initial Serial=0") - return - } + serial := account.Network.CurrentSerial() // should be 0 - key, err := wgtypes.GeneratePrivateKey() - if err != nil { - t.Fatal(err) - return - } - expectedPeerKey := key.PublicKey().String() - expectedSetupKey := setupKey.Key + setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userID, false) + if err != nil { + t.Fatal("error creating setup key") + return + } - peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ - Key: expectedPeerKey, - Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey}, - }) - if err != nil { - t.Errorf("expecting peer to be added, got failure %v", err) - return - } + if account.Network.Serial != 0 { + t.Errorf("expecting account network to have an initial Serial=0") + return + } - account, err = manager.Store.GetAccount(account.Id) - if err != nil { - t.Fatal(err) - return - } + account, err = manager.Store.GetAccount(account.Id) + if err != nil { + t.Fatal(err) + return + } - if peer.Key != expectedPeerKey { - t.Errorf("expecting just added peer to have key = %s, got %s", expectedPeerKey, peer.Key) - } + if c.allGroupIPv6Enabled { + unlock := manager.Store.AcquireAccountLock(account.Id) + groupAll, err := account.GetGroupAll() + if err != nil { + t.Fatal(err) + } + groupAll.IPv6Enabled = true + err = manager.Store.SaveAccount(account) + if err != nil { + t.Fatal(err) + } + unlock() + } - if !account.Network.Net.Contains(peer.IP) { - t.Errorf("expecting just added peer's IP %s to be in a network range %s", peer.IP.String(), account.Network.Net.String()) - } + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + return + } + expectedPeerKey := key.PublicKey().String() + expectedSetupKey := setupKey.Key - if peer.SetupKey != expectedSetupKey { - t.Errorf("expecting just added peer to have SetupKey = %s, got %s", expectedSetupKey, peer.SetupKey) - } + peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ + Key: expectedPeerKey, + Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey, Ipv6Supported: c.peerIPv6Supported}, + }) + if err != nil { + t.Errorf("expecting peer to be added, got failure %v", err) + return + } - if account.Network.CurrentSerial() != 1 { - t.Errorf("expecting Network Serial=%d to be incremented by 1 and be equal to %d when adding new peer to account", serial, account.Network.CurrentSerial()) - } - ev := getEvent(t, account.Id, manager, activity.PeerAddedWithSetupKey) + account, err = manager.Store.GetAccount(account.Id) + if err != nil { + t.Fatal(err) + return + } - assert.NotNil(t, ev) - assert.Equal(t, account.Id, ev.AccountID) - assert.Equal(t, peer.Name, ev.Meta["name"]) - assert.Equal(t, peer.FQDN(account.Domain), ev.Meta["fqdn"]) - assert.Equal(t, setupKey.Id, ev.InitiatorID) - assert.Equal(t, peer.ID, ev.TargetID) - assert.Equal(t, peer.IP.String(), fmt.Sprint(ev.Meta["ip"])) + if peer.Key != expectedPeerKey { + t.Errorf("expecting just added peer to have key = %s, got %s", expectedPeerKey, peer.Key) + } + + if !account.Network.Net.Contains(peer.IP) { + t.Errorf("expecting just added peer's IP %s to be in a network range %s", peer.IP.String(), account.Network.Net.String()) + } + + if peer.SetupKey != expectedSetupKey { + t.Errorf("expecting just added peer to have SetupKey = %s, got %s", expectedSetupKey, peer.SetupKey) + } + + if account.Network.CurrentSerial() != 1 { + t.Errorf("expecting Network Serial=%d to be incremented by 1 and be equal to %d when adding new peer to account", serial, account.Network.CurrentSerial()) + } + ev := getEvent(t, account.Id, manager, activity.PeerAddedWithSetupKey) + + assert.NotNil(t, ev) + assert.Equal(t, account.Id, ev.AccountID) + assert.Equal(t, peer.Name, ev.Meta["name"]) + assert.Equal(t, peer.FQDN(account.Domain), ev.Meta["fqdn"]) + assert.Equal(t, setupKey.Id, ev.InitiatorID) + assert.Equal(t, peer.ID, ev.TargetID) + assert.Equal(t, peer.IP.String(), fmt.Sprint(ev.Meta["ip"])) + assert.Equal(t, peer.V6Setting, nbpeer.V6Auto) + if c.peerIPv6Supported && c.allGroupIPv6Enabled { + assert.NotNil(t, peer.IP6) + } else { + assert.Nil(t, peer.IP6) + } + }) + } } func TestAccountManager_AddPeerWithUserID(t *testing.T) { @@ -1414,9 +1472,22 @@ func TestAccount_GetRoutesToSync(t *testing.T) { if err != nil { t.Fatal(err) } + _, prefix3, err := route.ParseNetwork("2001:db8:1234:5678::/64") + if err != nil { + t.Fatal(err) + } + _, prefix4, err := route.ParseNetwork("2001:db8:1234:6789::/64") + if err != nil { + t.Fatal(err) + } + peer2IP6 := net.ParseIP("2001:db8:abcd:1234::12") account := &Account{ Peers: map[string]*nbpeer.Peer{ - "peer-1": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-2": {Key: "peer-2", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-3": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, + "peer-1": { + ID: "peer-1", Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}, + }, + "peer-2": {ID: "peer-2", Key: "peer-2", Meta: nbpeer.PeerSystemMeta{GoOS: "linux", Ipv6Supported: true}, IP6: &peer2IP6}, + "peer-3": {ID: "peer-3", Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, }, Groups: map[string]*Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}}, Routes: map[string]*route.Route{ @@ -1426,7 +1497,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) { NetID: "network-1", Description: "network-1", Peer: "peer-1", - NetworkType: 0, + NetworkType: route.IPv4Network, Masquerade: false, Metric: 999, Enabled: true, @@ -1438,7 +1509,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) { NetID: "network-2", Description: "network-2", Peer: "peer-2", - NetworkType: 0, + NetworkType: route.IPv4Network, Masquerade: false, Metric: 999, Enabled: true, @@ -1450,7 +1521,31 @@ func TestAccount_GetRoutesToSync(t *testing.T) { NetID: "network-1", Description: "network-1", Peer: "peer-2", - NetworkType: 0, + NetworkType: route.IPv4Network, + Masquerade: false, + Metric: 999, + Enabled: true, + Groups: []string{"group1"}, + }, + "route-4": { + ID: "route-4", + Network: prefix3, + NetID: "network-3", + Description: "network-3", + Peer: "peer-1", + NetworkType: route.IPv6Network, + Masquerade: false, + Metric: 999, + Enabled: true, + Groups: []string{"group1"}, + }, + "route-5": { + ID: "route-5", + Network: prefix4, + NetID: "network-4", + Description: "network-4", + Peer: "peer-2", + NetworkType: route.IPv6Network, Masquerade: false, Metric: 999, Enabled: true, @@ -1459,17 +1554,30 @@ func TestAccount_GetRoutesToSync(t *testing.T) { }, } - routes := account.getRoutesToSync("peer-2", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-3"}}) + routes := account.getRoutesToSync("peer-1", []*nbpeer.Peer{{ID: "peer-2", Key: "peer-2"}, {ID: "peer-3", Key: "peer-3"}}) assert.Len(t, routes, 2) + routeIDs := make(map[string]struct{}, 2) for _, r := range routes { routeIDs[r.ID] = struct{}{} } + assert.Contains(t, routeIDs, "route-1") + assert.Contains(t, routeIDs, "route-2") + + routes = account.getRoutesToSync("peer-2", []*nbpeer.Peer{{ID: "peer-1", Key: "peer-1"}, {ID: "peer-3", Key: "peer-3"}}) + + assert.Len(t, routes, 3) + + routeIDs = make(map[string]struct{}, 2) + for _, r := range routes { + routeIDs[r.ID] = struct{}{} + } assert.Contains(t, routeIDs, "route-2") assert.Contains(t, routeIDs, "route-3") + assert.Contains(t, routeIDs, "route-5") - emptyRoutes := account.getRoutesToSync("peer-3", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-2"}}) + emptyRoutes := account.getRoutesToSync("peer-3", []*nbpeer.Peer{{ID: "peer-1", Key: "peer-1"}, {ID: "peer-2", Key: "peer-2"}}) assert.Len(t, emptyRoutes, 0) } diff --git a/management/server/activity/codes.go b/management/server/activity/codes.go index 20d8b22298c..111f23cad1a 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -146,7 +146,6 @@ const ( PeerIPv6InheritEnabled // PeerIPv6InheritDisabled indicates that IPv6 was disabled for a peer due to a change in group memberships. PeerIPv6InheritDisabled - RouteDisabledByDisablingV6 ) var activityMap = map[Activity]Code{ @@ -217,7 +216,6 @@ var activityMap = map[Activity]Code{ PeerIPv6Disabled: {"Peer IPv6 disabled by user", "peer.ipv6.manual_disable"}, PeerIPv6InheritDisabled: {"Peer IPv6 disabled due to change in group settings or membership", "peer.ipv6.inherit_disable"}, PeerIPv6InheritEnabled: {"Peer IPv6 enabled due to change in group settings or membership", "peer.ipv6.inherit_enable"}, - RouteDisabledByDisablingV6: {"IPv6 Route was disabled because IPv6 was disabled for the routing peer", "route.disable.ipv6_disabled"}, } // StringCode returns a string code of the activity diff --git a/management/server/dns_test.go b/management/server/dns_test.go index adaf375ceb8..b68e8f942ba 100644 --- a/management/server/dns_test.go +++ b/management/server/dns_test.go @@ -21,6 +21,7 @@ const ( dnsAdminUserID = "testingAdminUser" dnsRegularUserID = "testingRegularUser" dnsNSGroup1 = "ns1" + dnsNSGroup2 = "ns2" ) func TestGetDNSSettings(t *testing.T) { @@ -183,7 +184,7 @@ func TestGetNetworkMap_DNSConfigSync(t *testing.T) { require.NoError(t, err) require.Len(t, peer2AccountDNSConfig.DNSConfig.CustomZones, 1, "DNS config should have one custom zone for peers not in the disabled group") require.True(t, peer2AccountDNSConfig.DNSConfig.ServiceEnable, "DNS config should have DNS service enabled for peers not in the disabled group") - require.Len(t, peer2AccountDNSConfig.DNSConfig.NameServerGroups, 1, "updated DNS config should have 1 nameserver groups since peer 2 is part of the group All") + require.Len(t, peer2AccountDNSConfig.DNSConfig.NameServerGroups, 2, "updated DNS config should have 2 nameserver groups since peer 2 is part of the group All and supports IPv6") } func createDNSManager(t *testing.T) (*DefaultAccountManager, error) { @@ -213,14 +214,14 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro Key: dnsPeer1Key, Name: "test-host1@netbird.io", Meta: nbpeer.PeerSystemMeta{ - Hostname: "test-host1@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", + Hostname: "test-host1@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", Ipv6Supported: false, }, DNSLabel: dnsPeer1Key, @@ -229,17 +230,18 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro Key: dnsPeer2Key, Name: "test-host2@netbird.io", Meta: nbpeer.PeerSystemMeta{ - Hostname: "test-host2@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", - Ipv6Supported: false, + Hostname: "test-host2@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: true, }, - DNSLabel: dnsPeer2Key, + V6Setting: nbpeer.V6Enabled, + DNSLabel: dnsPeer2Key, } domain := "example.com" @@ -312,6 +314,20 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro Groups: []string{allGroup.ID}, } + account.NameServerGroups[dnsNSGroup2] = &dns.NameServerGroup{ + ID: dnsNSGroup2, + Name: "ns-group-2", + NameServers: []dns.NameServer{{ + IP: netip.MustParseAddr("2001:4860:4860:0:0:0:0:8888"), // Google DNS + NSType: dns.UDPNameServerType, + Port: dns.DefaultDNSPort, + }}, + Primary: false, + Domains: []string{"example.com"}, + Enabled: true, + Groups: []string{allGroup.ID}, + } + err = am.Store.SaveAccount(account) if err != nil { return nil, err diff --git a/management/server/group.go b/management/server/group.go index 9c3a097d658..a066f37fdc1 100644 --- a/management/server/group.go +++ b/management/server/group.go @@ -138,27 +138,22 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G } // Need to check whether IPv6 status has changed for all potentially affected peers. - peersToUpdate := removedPeers + peersToUpdate := make([]string, 0) + // If group previously had IPv6 enabled, need to check all old peers for changes in IPv6 status. + if exists && oldGroup.IPv6Enabled { + peersToUpdate = removedPeers + } + // If group IPv6 status changed, need to check all current peers, if it did not, but IPv6 is enabled, only check + // added peers, otherwise check no peers (as group can not affect IPv6 state). if exists && oldGroup.IPv6Enabled != newGroup.IPv6Enabled { peersToUpdate = append(peersToUpdate, newGroup.Peers...) - } else { + } else if newGroup.IPv6Enabled { peersToUpdate = append(peersToUpdate, addedPeers...) } - for _, peer := range peersToUpdate { - peerObj := account.GetPeer(peer) - update, err := am.DeterminePeerV6(account, peerObj) - if err != nil { - return err - } - if update { - account.UpdatePeer(peerObj) - if peerObj.IP6 != nil { - am.StoreEvent(userID, newGroup.ID, accountID, activity.PeerIPv6InheritEnabled, newGroup.EventMeta()) - } else { - am.StoreEvent(userID, newGroup.ID, accountID, activity.PeerIPv6InheritDisabled, newGroup.EventMeta()) - } - } + _, err = am.updatePeerIPv6Status(account, userID, newGroup, peersToUpdate) + if err != nil { + return err } account.Network.IncSerial() @@ -306,6 +301,14 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string) delete(account.Groups, groupID) + // Update IPv6 status of all group members if necessary. + if g.IPv6Enabled { + _, err = am.updatePeerIPv6Status(account, userId, g, g.Peers) + if err != nil { + return err + } + } + account.Network.IncSerial() if err = am.Store.SaveAccount(account); err != nil { return err @@ -337,6 +340,7 @@ func (am *DefaultAccountManager) ListGroups(accountID string) ([]*Group, error) } // GroupAddPeer appends peer to the group +// TODO Question for devs: Is this method dead code? I can't seem to find any usages outside of tests... func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string) error { unlock := am.Store.AcquireAccountLock(accountID) defer unlock() @@ -360,6 +364,14 @@ func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string) } if add { group.Peers = append(group.Peers, peerID) + + if group.IPv6Enabled { + // Update IPv6 status of added group member. + _, err = am.updatePeerIPv6Status(account, "", group, []string{peerID}) + if err != nil { + return err + } + } } account.Network.IncSerial() @@ -373,6 +385,7 @@ func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string) } // GroupDeletePeer removes peer from the group +// TODO Question for devs: Same as above, this seems like dead code func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerID string) error { unlock := am.Store.AcquireAccountLock(accountID) defer unlock() @@ -391,6 +404,15 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerID stri for i, itemID := range group.Peers { if itemID == peerID { group.Peers = append(group.Peers[:i], group.Peers[i+1:]...) + + if group.IPv6Enabled { + // Update IPv6 status of deleted group member. + _, err = am.updatePeerIPv6Status(account, "", group, []string{peerID}) + if err != nil { + return err + } + } + if err := am.Store.SaveAccount(account); err != nil { return err } @@ -401,3 +423,24 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerID stri return nil } + +func (am *DefaultAccountManager) updatePeerIPv6Status(account *Account, userID string, group *Group, peersToUpdate []string) (bool, error) { + updated := false + for _, peer := range peersToUpdate { + peerObj := account.GetPeer(peer) + update, err := am.DeterminePeerV6(account, peerObj) + if err != nil { + return false, err + } + if update { + updated = true + account.UpdatePeer(peerObj) + if peerObj.IP6 != nil { + am.StoreEvent(userID, group.ID, account.Id, activity.PeerIPv6InheritEnabled, group.EventMeta()) + } else { + am.StoreEvent(userID, group.ID, account.Id, activity.PeerIPv6InheritDisabled, group.EventMeta()) + } + } + } + return updated, nil +} diff --git a/management/server/group_test.go b/management/server/group_test.go index e2051a65611..df85fcf6ff7 100644 --- a/management/server/group_test.go +++ b/management/server/group_test.go @@ -2,6 +2,8 @@ package server import ( "errors" + nbpeer "github.com/netbirdio/netbird/management/server/peer" + "github.com/stretchr/testify/require" "testing" nbdns "github.com/netbirdio/netbird/dns" @@ -11,6 +13,8 @@ import ( const ( groupAdminUserID = "testingAdminUser" + groupPeer1Key = "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8=" + groupPeer2Key = "/yF0+vCfv+mRR5k0dca0TrGdO/oiNeAI58gToZm5NyI=" ) func TestDefaultAccountManager_DeleteGroup(t *testing.T) { @@ -19,7 +23,7 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) { t.Error("failed to create account manager") } - account, err := initTestGroupAccount(am) + _, account, err := initTestGroupAccount(am) if err != nil { t.Error("failed to init testing account") } @@ -90,10 +94,136 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) { } } -func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) { +func TestDefaultAccountManager_GroupIPv6Consistency(t *testing.T) { + am, err := createManager(t) + if err != nil { + t.Error("failed to create account manager") + } + + peers, account, err := initTestGroupAccount(am) + peer1Id := peers[0] + peer2Id := peers[1] + if err != nil { + t.Error("failed to init testing account") + } + + group := account.GetGroup("grp-for-ipv6") + + // First, add one member to the IPv6 group before enabling IPv6. + group.Peers = append(group.Peers, peer1Id) + err = am.SaveGroup(account.Id, groupAdminUserID, group) + require.NoError(t, err, "unable to update group") + account, err = am.Store.GetAccount(account.Id) + require.NoError(t, err, "unable to update account") + group = account.GetGroup("grp-for-ipv6") + require.Nil(t, account.Peers[peer1Id].IP6, "peer1 should not have an IPv6 address if the group doesn't have it enabled.") + require.Nil(t, account.Peers[peer2Id].IP6, "peer2 should not have an IPv6 address.") + + // Now, enable IPv6. + group.IPv6Enabled = true + err = am.SaveGroup(account.Id, groupAdminUserID, group) + require.NoError(t, err, "unable to update group") + account, err = am.Store.GetAccount(account.Id) + require.NoError(t, err, "unable to update account") + group = account.GetGroup("grp-for-ipv6") + require.NotNil(t, account.Peers[peer1Id].IP6, "peer1 should have an IPv6 address as it is a member of the IPv6-enabled group.") + require.Nil(t, account.Peers[peer2Id].IP6, "peer2 should not have an IPv6 address as it is not a member of the IPv6-enabled group.") + + // Add the second peer. + group.Peers = append(group.Peers, peer2Id) + err = am.SaveGroup(account.Id, groupAdminUserID, group) + require.NoError(t, err, "unable to update group") + account, err = am.Store.GetAccount(account.Id) + require.NoError(t, err, "unable to update account") + group = account.GetGroup("grp-for-ipv6") + require.NotNil(t, account.Peers[peer1Id].IP6, "peer1 should have an IPv6 address as it is a member of the IPv6-enabled group.") + require.NotNil(t, account.Peers[peer2Id].IP6, "peer2 should have an IPv6 address as it is a member of the IPv6-enabled group.") + + // Disable IPv6 and simultaneously delete the first peer. + group.IPv6Enabled = false + group.Peers = group.Peers[1:] + err = am.SaveGroup(account.Id, groupAdminUserID, group) + require.NoError(t, err, "unable to update group") + account, err = am.Store.GetAccount(account.Id) + require.NoError(t, err, "unable to update account") + group = account.GetGroup("grp-for-ipv6") + require.Nil(t, account.Peers[peer1Id].IP6, "peer1 should not have an IPv6 address as it is not a member of any IPv6-enabled group.") + require.Nil(t, account.Peers[peer2Id].IP6, "peer2 should not have an IPv6 address as the group has IPv6 disabled.") + + // Enable IPv6 and simultaneously add the first peer again. + group.IPv6Enabled = true + group.Peers = append(group.Peers, peer1Id) + err = am.SaveGroup(account.Id, groupAdminUserID, group) + require.NoError(t, err, "unable to update group") + account, err = am.Store.GetAccount(account.Id) + require.NoError(t, err, "unable to update account") + group = account.GetGroup("grp-for-ipv6") + require.NotNil(t, account.Peers[peer1Id].IP6, "peer1 should have an IPv6 address as it is a member of the IPv6-enabled group.") + require.NotNil(t, account.Peers[peer2Id].IP6, "peer2 should have an IPv6 address as it is a member of the IPv6-enabled group.") + + // Force disable IPv6. + account.GetPeer(peer1Id).V6Setting = nbpeer.V6Disabled + account.GetPeer(peer2Id).V6Setting = nbpeer.V6Disabled + account.UpdatePeer(account.Peers[peer1Id]) + account.UpdatePeer(account.Peers[peer2Id]) + err = am.Store.SaveAccount(account) + require.NoError(t, err, "unable to update account") + account, err = am.Store.GetAccount(account.Id) + require.NoError(t, err, "unable to update account") + group = account.GetGroup("grp-for-ipv6") + require.Nil(t, account.GetPeer(peer1Id).IP6, "peer1 should not have an IPv6 address as it is force disabled.") + require.Nil(t, account.GetPeer(peer2Id).IP6, "peer2 should not have an IPv6 address as it is force disabled.") + + // Delete Group. + err = am.DeleteGroup(account.Id, groupAdminUserID, group.ID) + require.NoError(t, err, "unable to delete group") + account, err = am.Store.GetAccount(account.Id) + require.NoError(t, err, "unable to update account") + group = account.GetGroup("grp-for-ipv6") + require.Nil(t, group, "Group should no longer exist.") + require.Nil(t, account.Peers[peer1Id].IP6, "peer1 should not have an IPv6 address as the only IPv6-enabled group was deleted.") + require.Nil(t, account.Peers[peer2Id].IP6, "peer2 should not have an IPv6 address as the only IPv6-enabled group was deleted.") +} + +func initTestGroupAccount(am *DefaultAccountManager) ([]string, *Account, error) { accountID := "testingAcc" domain := "example.com" + peer1 := &nbpeer.Peer{ + Key: peer1Key, + Name: "peer1", + Meta: nbpeer.PeerSystemMeta{ + Hostname: "test-host1@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: true, + }, + V6Setting: nbpeer.V6Auto, + DNSLabel: groupPeer1Key, + } + peer2 := &nbpeer.Peer{ + Key: peer2Key, + Name: "peer2", + Meta: nbpeer.PeerSystemMeta{ + Hostname: "test-host2@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: true, + }, + V6Setting: nbpeer.V6Auto, + DNSLabel: groupPeer2Key, + } + groupForRoute := &Group{ ID: "grp-for-route", AccountID: "account-id", @@ -137,11 +267,19 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) { groupForIntegration := &Group{ ID: "grp-for-integration", AccountID: "account-id", - Name: "Group for users", + Name: "Group for integration", Issued: GroupIssuedIntegration, Peers: make([]string, 0), } + groupForIPv6 := &Group{ + ID: "grp-for-ipv6", + AccountID: "account-id", + Name: "Group for IPv6", + Issued: GroupIssuedAPI, + Peers: make([]string, 0), + } + routeResource := &route.Route{ ID: "example route", Groups: []string{groupForRoute.ID}, @@ -180,7 +318,7 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) { err := am.Store.SaveAccount(account) if err != nil { - return nil, err + return nil, nil, err } _ = am.SaveGroup(accountID, groupAdminUserID, groupForRoute) @@ -189,6 +327,11 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) { _ = am.SaveGroup(accountID, groupAdminUserID, groupForSetupKeys) _ = am.SaveGroup(accountID, groupAdminUserID, groupForUsers) _ = am.SaveGroup(accountID, groupAdminUserID, groupForIntegration) + _ = am.SaveGroup(accountID, groupAdminUserID, groupForIPv6) + peer1, _, _ = am.AddPeer(setupKey.Key, user.Id, peer1) + peer2, _, _ = am.AddPeer(setupKey.Key, user.Id, peer2) + + account, err = am.Store.GetAccount(account.Id) - return am.Store.GetAccount(account.Id) + return []string{peer1.ID, peer2.ID}, account, err } diff --git a/management/server/http/groups_handler.go b/management/server/http/groups_handler.go index 9154d7f1f2c..b4cf6ad0567 100644 --- a/management/server/http/groups_handler.go +++ b/management/server/http/groups_handler.go @@ -109,7 +109,15 @@ func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) { util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w) return } + deduplicatedPeers := make(map[string]struct{}) for _, peer := range peers { + deduplicatedPeers[peer] = struct{}{} + } + if len(deduplicatedPeers) != len(peers) { + util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w) + return + } + for peer, _ := range deduplicatedPeers { if slices.Contains(allGroup.Peers, peer) { continue } diff --git a/management/server/network_test.go b/management/server/network_test.go index da164d5b428..c4a4a34c8fc 100644 --- a/management/server/network_test.go +++ b/management/server/network_test.go @@ -1,6 +1,7 @@ package server import ( + "github.com/stretchr/testify/require" "net" "testing" @@ -8,11 +9,19 @@ import ( ) func TestNewNetwork(t *testing.T) { - network := NewNetwork(false) + network := NewNetwork(true) // generated net should be a subnet of a larger 100.64.0.0/10 net ipNet := net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 192, 0, 0}} - assert.Equal(t, ipNet.Contains(network.Net.IP), true) + assert.True(t, ipNet.Contains(network.Net.IP)) + + // generated IPv6 net should be a subnet of the fd00:b14d::/32 prefix. + _, ipNet6, err := net.ParseCIDR("fd00:b14d::/32") + require.NoError(t, err, "unable to parse IPv6 prefix") + assert.True(t, ipNet6.Contains(network.Net6.IP)) + // IPv6 prefix should be of size /64 + ones, _ := network.Net6.Mask.Size() + assert.Equal(t, ones, 64) } func TestAllocatePeerIP(t *testing.T) { @@ -38,6 +47,32 @@ func TestAllocatePeerIP(t *testing.T) { } } +func TestAllocatePeerIP6(t *testing.T) { + _, ipNet, err := net.ParseCIDR("2001:db8:abcd:1234::/64") + require.NoError(t, err, "unable to parse IPv6 prefix") + var ips []net.IP + // Yeah, we better not check all 2^64 possible addresses, just generating a bunch of addresses should hopefully + // reveal any possible bugs in the RNG. + for i := 0; i < 252; i++ { + ip, err := AllocatePeerIP6(*ipNet, ips) + if err != nil { + t.Fatal(err) + } + ips = append(ips, ip) + } + + assert.Len(t, ips, 252) + + uniq := make(map[string]struct{}) + for _, ip := range ips { + if _, ok := uniq[ip.String()]; !ok { + uniq[ip.String()] = struct{}{} + } else { + t.Errorf("found duplicate IP %s", ip.String()) + } + } +} + func TestGenerateIPs(t *testing.T) { ipNet := net.IPNet{IP: net.ParseIP("100.64.0.0"), Mask: net.IPMask{255, 255, 255, 0}} ips, ipsLen := generateIPs(&ipNet, map[string]struct{}{"100.64.0.0": {}}) diff --git a/management/server/peer.go b/management/server/peer.go index 63c0cf00bac..8656b032bab 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -151,9 +151,11 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected // Determines the current IPv6 status of the peer (including checks for inheritance) and generates a new or removes an // existing IPv6 address if necessary. +// Additionally, disables IPv6 routes if peer no longer has an IPv6 address. // Note that this change does not get persisted here. // -// Returns a boolean that indicates whether the peer changed and needs to be updated in the data source. +// Returns a boolean that indicates whether the peer and/or the account changed and needs to be updated in the data +// source. func (am *DefaultAccountManager) DeterminePeerV6(account *Account, peer *nbpeer.Peer) (bool, error) { v6Setting := peer.V6Setting if peer.V6Setting == nbpeer.V6Auto { @@ -194,6 +196,13 @@ func (am *DefaultAccountManager) DeterminePeerV6(account *Account, peer *nbpeer. return true, nil } else if v6Setting == nbpeer.V6Disabled && peer.IP6 != nil { peer.IP6 = nil + + for _, route := range account.Routes { + if route.NetworkType == nbroute.IPv6Network { + route.Enabled = false + account.Routes[route.ID] = route + } + } return true, nil } return false, nil @@ -230,14 +239,6 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nb am.StoreEvent(userID, peer.IP6.String(), account.Id, activity.PeerIPv6Enabled, peer.EventMeta(am.GetDNSDomain())) } else if v6StatusChanged && peer.IP6 == nil { am.StoreEvent(userID, prevV6.String(), account.Id, activity.PeerIPv6Disabled, peer.EventMeta(am.GetDNSDomain())) - - for _, route := range account.Routes { - if route.Peer == peer.ID && route.NetworkType == nbroute.IPv6Network { - route.Enabled = false - account.Routes[route.ID] = route - am.StoreEvent(userID, prevV6.String(), account.Id, activity.RouteDisabledByDisablingV6, peer.EventMeta(am.GetDNSDomain())) - } - } } } @@ -503,7 +504,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P CreatedAt: registrationTime, LoginExpirationEnabled: addedByUser, Ephemeral: ephemeral, - V6Setting: "", // corresponds to "auto" + V6Setting: peer.V6Setting, // empty string "" corresponds to "auto" } if account.Settings.Extra != nil { @@ -676,7 +677,10 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw shouldStoreAccount = true } - updated = disableNoLongerSupportedFeatures(account, peer) + updated, err = am.DeterminePeerV6(account, peer) + if err != nil { + return nil, nil, err + } if updated { shouldStoreAccount = true } @@ -699,17 +703,12 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil } -func disableNoLongerSupportedFeatures(account *Account, peer *nbpeer.Peer) bool { +// Applies changes to IPv6 addresses +func applyIPv6SupportStatus(account *Account, peer *nbpeer.Peer) bool { if !peer.Meta.Ipv6Supported && peer.IP6 != nil { peer.IP6 = nil // Reset V6 setting to default "auto" so that we maintain consistent state if IPv6 is ever supported again. peer.V6Setting = nbpeer.V6Auto - for _, route := range account.Routes { - if route.NetworkType == nbroute.IPv6Network { - route.Enabled = false - account.Routes[route.ID] = route - } - } return true } return false diff --git a/management/server/peer_test.go b/management/server/peer_test.go index ee84ea47dab..2939519db8e 100644 --- a/management/server/peer_test.go +++ b/management/server/peer_test.go @@ -1,6 +1,7 @@ package server import ( + "github.com/stretchr/testify/require" "testing" "time" @@ -136,6 +137,108 @@ func TestAccountManager_GetNetworkMap(t *testing.T) { } } +func TestDefaultAccountManager_DeterminePeerV6(t *testing.T) { + manager, err := createManager(t) + if err != nil { + t.Fatal(err) + return + } + + expectedId := "test_account" + userId := "account_creator" + account, err := createAccount(manager, expectedId, userId, "") + if err != nil { + t.Fatal(err) + } + + setupKey, err := manager.CreateSetupKey(account.Id, "test-key", SetupKeyReusable, time.Hour, nil, 999, userId, false) + if err != nil { + t.Fatal("error creating setup key") + return + } + + peerKey1, err := wgtypes.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + return + } + + peer1, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ + Key: peerKey1.PublicKey().String(), + Meta: nbpeer.PeerSystemMeta{ + Hostname: "test-peer-1", + Ipv6Supported: true, + }, + }) + if err != nil { + t.Errorf("expecting peer to be added, got failure %v", err) + return + } + + peerKey2, err := wgtypes.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + return + } + + peer2, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ + Key: peerKey2.PublicKey().String(), + Meta: nbpeer.PeerSystemMeta{ + Hostname: "test-peer-2", + Ipv6Supported: false, + }, + }) + + if err != nil { + t.Errorf("expecting peer to be added, got failure %v", err) + return + } + + account, err = manager.Store.GetAccount(account.Id) + require.NoError(t, err, "unable to fetch updated account") + + // Check if automatic setting defaults to "false". + // (Other tests for interactions between the automatic setting and group/route memberships are already covered in + // group_test.go and route_test.go) + _, err = manager.DeterminePeerV6(account, peer1) + require.NoError(t, err, "unable to determine effective peer IPv6 status") + _, err = manager.DeterminePeerV6(account, peer2) + require.NoError(t, err, "unable to determine effective peer IPv6 status") + require.Nil(t, peer1.IP6, "peer1 IPv6 address did not default to nil.") + require.Nil(t, peer1.IP6, "peer2 IPv6 address did not default to nil.") + + peer1.V6Setting = nbpeer.V6Disabled + peer2.V6Setting = nbpeer.V6Disabled + _, err = manager.DeterminePeerV6(account, peer1) + _, err = manager.DeterminePeerV6(account, peer2) + require.Nil(t, peer1.IP6, "peer1 IPv6 address is not nil even though it is force disabled.") + require.Nil(t, peer2.IP6, "peer2 IPv6 address is not nil even though it is force disabled and unsupported.") + + peer1.V6Setting = nbpeer.V6Enabled + peer2.V6Setting = nbpeer.V6Enabled + _, err = manager.DeterminePeerV6(account, peer1) + _, err = manager.DeterminePeerV6(account, peer2) + require.NotNil(t, peer1.IP6, "peer1 IPv6 address is nil even though it is force enabled.") + require.Nil(t, peer2.IP6, "peer2 IPv6 address is not nil even though it is unsupported.") + + // Test whether disabling IPv6 will disable IPv6 routes. + allGroup, err := account.GetGroupAll() + require.NoError(t, err, "unable to retrieve all group") + route, err := manager.CreateRoute(account.Id, "2001:db8:2345:6789::/64", peer1.ID, make([]string, 0), "testroute", "testnet", false, 9999, []string{allGroup.ID}, true, userID) + require.NoError(t, err, "unable to create test IPv6 route") + require.True(t, route.Enabled, "created IPv6 test route should be enabled") + + peer1.V6Setting = nbpeer.V6Disabled + peer1, err = manager.UpdatePeer(account.Id, userID, peer1) + require.NoError(t, err, "unable to update peer") + + account, err = manager.Store.GetAccount(account.Id) + require.NoError(t, err, "unable to fetch updated account") + + route = account.Routes[route.ID] + require.False(t, route.Enabled, "disabling IPv6 for a peer should disable all of its IPv6 routes.") +} + func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) { // TODO: disable until we start use policy again t.Skip() diff --git a/management/server/policy_test.go b/management/server/policy_test.go index 50a66a15d34..6d3914b28b4 100644 --- a/management/server/policy_test.go +++ b/management/server/policy_test.go @@ -13,16 +13,20 @@ import ( ) func TestAccount_getPeersByPolicy(t *testing.T) { + peerAIP6 := net.ParseIP("2001:db8:abcd:1234::2") + peerBIP6 := net.ParseIP("2001:db8:abcd:1234::3") account := &Account{ Peers: map[string]*nbpeer.Peer{ "peerA": { ID: "peerA", IP: net.ParseIP("100.65.14.88"), + IP6: &peerAIP6, Status: &nbpeer.PeerStatus{}, }, "peerB": { ID: "peerB", IP: net.ParseIP("100.65.80.39"), + IP6: &peerBIP6, Status: &nbpeer.PeerStatus{}, }, "peerC": { @@ -171,6 +175,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) { }, { PeerIP: "100.65.14.88", + PeerIP6: "2001:db8:abcd:1234::2", Direction: firewallRuleDirectionIN, Action: "accept", Protocol: "all", @@ -178,6 +183,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) { }, { PeerIP: "100.65.14.88", + PeerIP6: "2001:db8:abcd:1234::2", Direction: firewallRuleDirectionOUT, Action: "accept", Protocol: "all", diff --git a/management/server/route.go b/management/server/route.go index cb505d6ad93..bda4584282e 100644 --- a/management/server/route.go +++ b/management/server/route.go @@ -242,6 +242,16 @@ func (am *DefaultAccountManager) SaveRoute(accountID, userID string, routeToSave return status.Errorf(status.InvalidArgument, "peer with ID and peer groups should not be provided at the same time") } + if routeToSave.Peer != "" { + peer := account.GetPeer(routeToSave.Peer) + if peer == nil { + return status.Errorf(status.InvalidArgument, "provided peer does not exist") + } + if routeToSave.NetworkType == route.IPv6Network && routeToSave.Enabled && (!peer.Meta.Ipv6Supported || peer.V6Setting == nbpeer.V6Disabled) { + return status.Errorf(status.InvalidArgument, "peer with IPv6 disabled can't be used for IPv6 route") + } + } + if len(routeToSave.PeerGroups) > 0 { err = validateGroups(routeToSave.PeerGroups, account.Groups) if err != nil { @@ -330,8 +340,8 @@ func (am *DefaultAccountManager) DeleteRoute(accountID, routeID, userID string) } delete(account.Routes, routeID) - // IPv6 route must only be created with IPv6 enabled peers, creating an IPv6 enabled route may enable IPv6 for - // peers with V6Setting = Auto. + // If the route was an IPv6 route, deleting it may update the automatic IPv6 enablement status of its routing peers, + // check if this is the case and update accordingly. if routy.Peer != "" && routy.Enabled && routy.NetworkType == route.IPv6Network { oldPeer := account.GetPeer(routy.Peer) if oldPeer.V6Setting == nbpeer.V6Auto { diff --git a/management/server/route_test.go b/management/server/route_test.go index 760df1abcc1..6832fbfb0e6 100644 --- a/management/server/route_test.go +++ b/management/server/route_test.go @@ -318,6 +318,88 @@ func TestCreateRoute(t *testing.T) { errFunc: require.Error, shouldCreate: false, }, + { + name: "IPv6 route on peer with disabled IPv6 should fail", + inputArgs: input{ + network: "2001:db8:7654:3210::/64", + netID: "NewId", + peerKey: peer4ID, + description: "", + masquerade: false, + metric: 9999, + enabled: true, + groups: []string{routeGroup1}, + }, + errFunc: require.Error, + shouldCreate: false, + }, + { + name: "IPv6 route on peer with unsupported IPv6 should fail", + inputArgs: input{ + network: "2001:db8:7654:3210::/64", + netID: "NewId", + peerKey: peer5ID, + description: "", + masquerade: false, + metric: 9999, + enabled: true, + groups: []string{routeGroup1}, + }, + errFunc: require.Error, + shouldCreate: false, + }, + { + name: "IPv6 route on peer with automatic IPv6 setting should succeed", + inputArgs: input{ + network: "2001:db8:7654:3210::/64", + netID: "NewId", + peerKey: peer1ID, + description: "", + masquerade: false, + metric: 9999, + enabled: true, + groups: []string{routeGroup1}, + }, + errFunc: require.NoError, + shouldCreate: true, + expectedRoute: &route.Route{ + Network: netip.MustParsePrefix("2001:db8:7654:3210::/64"), + NetworkType: route.IPv6Network, + NetID: "NewId", + Peer: peer1ID, + Description: "", + Masquerade: false, + Metric: 9999, + Enabled: true, + Groups: []string{routeGroup1}, + }, + }, + { + name: "IPv6 route on peer with force enabled IPv6 setting should succeed", + inputArgs: input{ + network: "2001:db8:7654:3211::/64", + netID: "NewId", + peerKey: peer2ID, + description: "", + masquerade: false, + metric: 9999, + enabled: true, + groups: []string{routeGroup1}, + }, + errFunc: require.NoError, + shouldCreate: true, + expectedRoute: &route.Route{ + Network: netip.MustParsePrefix("2001:db8:7654:3211::/64"), + NetworkType: route.IPv6Network, + NetID: "NewId", + Peer: peer2ID, + Description: "", + Masquerade: false, + Metric: 9999, + Enabled: true, + Groups: []string{routeGroup1}, + }, + }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { @@ -376,6 +458,8 @@ func TestCreateRoute(t *testing.T) { func TestSaveRoute(t *testing.T) { validPeer := peer2ID validUsedPeer := peer5ID + ipv6DisabledPeer := peer4ID + ipv6UnsupportedPeer := peer5ID invalidPeer := "nonExisting" validPrefix := netip.MustParsePrefix("192.168.0.0/24") invalidPrefix, _ := netip.ParsePrefix("192.168.0.0/34") @@ -466,7 +550,7 @@ func TestSaveRoute(t *testing.T) { }, }, { - name: "Both peer and peers_roup Provided Should Fail", + name: "Both peer and peers_group Provided Should Fail", existingRoute: &route.Route{ ID: "testingRoute", Network: netip.MustParsePrefix("192.168.0.0/16"), @@ -516,6 +600,38 @@ func TestSaveRoute(t *testing.T) { newPeer: &invalidPeer, errFunc: require.Error, }, + { + name: "IPv6 disabled host should not be allowed as peer for IPv6 route", + existingRoute: &route.Route{ + ID: "testingRoute", + Network: netip.MustParsePrefix("2001:db8:4321:5678::/64"), + NetID: validNetID, + NetworkType: route.IPv6Network, + Description: "super", + Masquerade: false, + Metric: 9999, + Enabled: true, + Groups: []string{routeGroup1}, + }, + newPeer: &ipv6DisabledPeer, + errFunc: require.Error, + }, + { + name: "IPv6 unsupported host should not be allowed as peer for IPv6 route", + existingRoute: &route.Route{ + ID: "testingRoute", + Network: netip.MustParsePrefix("2001:db8:4321:5678::/64"), + NetID: validNetID, + NetworkType: route.IPv6Network, + Description: "super", + Masquerade: false, + Metric: 9999, + Enabled: true, + Groups: []string{routeGroup1}, + }, + newPeer: &ipv6UnsupportedPeer, + errFunc: require.Error, + }, { name: "Invalid Metric Should Fail", existingRoute: &route.Route{ @@ -771,7 +887,7 @@ func TestDeleteRoute(t *testing.T) { ID: "testingRoute", Network: netip.MustParsePrefix("192.168.0.0/16"), NetworkType: route.IPv4Network, - Peer: peer1Key, + Peer: peer1ID, Description: "super", Masquerade: false, Metric: 9999, @@ -811,6 +927,77 @@ func TestDeleteRoute(t *testing.T) { } } +func TestRouteIPv6Consistency(t *testing.T) { + testingRoute := &route.Route{ + ID: "testingRoute", + Network: netip.MustParsePrefix("2001:db8:0987:6543::/64"), + NetworkType: route.IPv6Network, + NetID: existingRouteID, + Peer: peer1ID, + Description: "super", + Masquerade: false, + Metric: 9999, + Enabled: false, + Groups: []string{routeGroup1}, + } + + am, err := createRouterManager(t) + if err != nil { + t.Error("failed to create account manager") + } + + account, err := initTestRouteAccount(t, am) + if err != nil { + t.Error("failed to init testing account") + } + + account.Routes[testingRoute.ID] = testingRoute + + err = am.Store.SaveAccount(account) + if err != nil { + t.Error("failed to save account") + } + + savedAccount, err := am.Store.GetAccount(account.Id) + if err != nil { + t.Error("failed to retrieve saved account with error: ", err) + } + + testingRoute = savedAccount.Routes[testingRoute.ID] + testingRoute.Enabled = true + + err = am.SaveRoute(account.Id, userID, testingRoute) + if err != nil { + t.Error("failed to save route") + } + + savedAccount, err = am.Store.GetAccount(account.Id) + if err != nil { + t.Error("failed to retrieve saved account with error: ", err) + } + + peer := savedAccount.GetPeer(peer1ID) + require.NotNil(t, peer.IP6, "peer with enabled IPv6 route in automatic setting must have IPv6 active.") + + err = am.DeleteRoute(account.Id, testingRoute.ID, userID) + if err != nil { + t.Error("deleting route failed with error: ", err) + } + + savedAccount, err = am.Store.GetAccount(account.Id) + if err != nil { + t.Error("failed to retrieve saved account with error: ", err) + } + + _, found := savedAccount.Routes[testingRoute.ID] + if found { + t.Error("route shouldn't be found after delete") + } + + peer = savedAccount.GetPeer(peer1ID) + require.Nil(t, peer.IP6, "disabling the only IPv6 route for a peer with automatic IPv6 setting should disable IPv6") +} + func TestGetNetworkMap_RouteSyncPeerGroups(t *testing.T) { baseRoute := &route.Route{ Network: netip.MustParsePrefix("192.168.0.0/16"), @@ -1053,15 +1240,15 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er Name: "test-host1@netbird.io", UserID: userID, Meta: nbpeer.PeerSystemMeta{ - Hostname: "test-host1@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", - Ipv6Supported: false, + Hostname: "test-host1@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: true, }, Status: &nbpeer.PeerStatus{}, } @@ -1072,25 +1259,32 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er if err != nil { return nil, err } + ips = account.getTakenIP6s() + peer2IP6, err := AllocatePeerIP6(account.Network.Net, ips) + if err != nil { + return nil, err + } peer2 := &nbpeer.Peer{ IP: peer2IP, + IP6: &peer2IP6, ID: peer2ID, Key: peer2Key, Name: "test-host2@netbird.io", UserID: userID, Meta: nbpeer.PeerSystemMeta{ - Hostname: "test-host2@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", - Ipv6Supported: false, + Hostname: "test-host2@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: true, }, - Status: &nbpeer.PeerStatus{}, + V6Setting: nbpeer.V6Enabled, + Status: &nbpeer.PeerStatus{}, } account.Peers[peer2.ID] = peer2 @@ -1099,25 +1293,32 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er if err != nil { return nil, err } + ips = account.getTakenIP6s() + peer3IP6, err := AllocatePeerIP6(account.Network.Net, ips) + if err != nil { + return nil, err + } peer3 := &nbpeer.Peer{ IP: peer3IP, + IP6: &peer3IP6, ID: peer3ID, Key: peer3Key, Name: "test-host3@netbird.io", UserID: userID, Meta: nbpeer.PeerSystemMeta{ - Hostname: "test-host3@netbird.io", - GoOS: "darwin", - Kernel: "Darwin", - Core: "13.4.1", - Platform: "arm64", - OS: "darwin", - WtVersion: "development", - UIVersion: "development", - Ipv6Supported: false, + Hostname: "test-host3@netbird.io", + GoOS: "darwin", + Kernel: "Darwin", + Core: "13.4.1", + Platform: "arm64", + OS: "darwin", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: true, }, - Status: &nbpeer.PeerStatus{}, + V6Setting: nbpeer.V6Enabled, + Status: &nbpeer.PeerStatus{}, } account.Peers[peer3.ID] = peer3 @@ -1134,14 +1335,14 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er Name: "test-host4@netbird.io", UserID: userID, Meta: nbpeer.PeerSystemMeta{ - Hostname: "test-host4@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", + Hostname: "test-host4@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", Ipv6Supported: false, }, Status: &nbpeer.PeerStatus{}, @@ -1161,17 +1362,18 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er Name: "test-host4@netbird.io", UserID: userID, Meta: nbpeer.PeerSystemMeta{ - Hostname: "test-host4@netbird.io", - GoOS: "linux", - Kernel: "Linux", - Core: "21.04", - Platform: "x86_64", - OS: "Ubuntu", - WtVersion: "development", - UIVersion: "development", - Ipv6Supported: false, + Hostname: "test-host4@netbird.io", + GoOS: "linux", + Kernel: "Linux", + Core: "21.04", + Platform: "x86_64", + OS: "Ubuntu", + WtVersion: "development", + UIVersion: "development", + Ipv6Supported: true, }, - Status: &nbpeer.PeerStatus{}, + Status: &nbpeer.PeerStatus{}, + V6Setting: nbpeer.V6Disabled, } account.Peers[peer5.ID] = peer5 From a8bbc2fe1f482bd451c313008a6f993e727b4437 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Mon, 11 Mar 2024 02:51:49 +0100 Subject: [PATCH 39/42] Fix linting issues --- client/firewall/nftables/manager_linux_test.go | 4 ++-- management/server/group_test.go | 1 - management/server/http/groups_handler.go | 2 +- management/server/peer.go | 11 ----------- management/server/peer_test.go | 6 +++++- 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/client/firewall/nftables/manager_linux_test.go b/client/firewall/nftables/manager_linux_test.go index ebf4570b4b1..fd7fced140e 100644 --- a/client/firewall/nftables/manager_linux_test.go +++ b/client/firewall/nftables/manager_linux_test.go @@ -387,7 +387,7 @@ func TestNftablesManagerAddressReset6(t *testing.T) { testClient := &nftables.Conn{} - rule, err := manager.AddFiltering( + _, err = manager.AddFiltering( ip, fw.ProtocolTCP, nil, @@ -443,7 +443,7 @@ func TestNftablesManagerAddressReset6(t *testing.T) { require.True(t, manager.V6Active(), "IPv6 is not active even though it should be.") - rule, err = manager.AddFiltering( + rule, err := manager.AddFiltering( ip, fw.ProtocolTCP, nil, diff --git a/management/server/group_test.go b/management/server/group_test.go index df85fcf6ff7..035fbb564ac 100644 --- a/management/server/group_test.go +++ b/management/server/group_test.go @@ -157,7 +157,6 @@ func TestDefaultAccountManager_GroupIPv6Consistency(t *testing.T) { require.NoError(t, err, "unable to update group") account, err = am.Store.GetAccount(account.Id) require.NoError(t, err, "unable to update account") - group = account.GetGroup("grp-for-ipv6") require.NotNil(t, account.Peers[peer1Id].IP6, "peer1 should have an IPv6 address as it is a member of the IPv6-enabled group.") require.NotNil(t, account.Peers[peer2Id].IP6, "peer2 should have an IPv6 address as it is a member of the IPv6-enabled group.") diff --git a/management/server/http/groups_handler.go b/management/server/http/groups_handler.go index b4cf6ad0567..18de6fc8b9a 100644 --- a/management/server/http/groups_handler.go +++ b/management/server/http/groups_handler.go @@ -117,7 +117,7 @@ func (h *GroupsHandler) UpdateGroup(w http.ResponseWriter, r *http.Request) { util.WriteError(status.Errorf(status.InvalidArgument, "updating group ALL is not allowed"), w) return } - for peer, _ := range deduplicatedPeers { + for peer := range deduplicatedPeers { if slices.Contains(allGroup.Peers, peer) { continue } diff --git a/management/server/peer.go b/management/server/peer.go index 8656b032bab..4a6fe31c10b 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -703,17 +703,6 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil } -// Applies changes to IPv6 addresses -func applyIPv6SupportStatus(account *Account, peer *nbpeer.Peer) bool { - if !peer.Meta.Ipv6Supported && peer.IP6 != nil { - peer.IP6 = nil - // Reset V6 setting to default "auto" so that we maintain consistent state if IPv6 is ever supported again. - peer.V6Setting = nbpeer.V6Auto - return true - } - return false -} - func checkIfPeerOwnerIsBlocked(peer *nbpeer.Peer, account *Account) error { if peer.AddedWithSSOLogin() { user, err := account.FindUser(peer.UserID) diff --git a/management/server/peer_test.go b/management/server/peer_test.go index 2939519db8e..6a50378f74a 100644 --- a/management/server/peer_test.go +++ b/management/server/peer_test.go @@ -210,14 +210,18 @@ func TestDefaultAccountManager_DeterminePeerV6(t *testing.T) { peer1.V6Setting = nbpeer.V6Disabled peer2.V6Setting = nbpeer.V6Disabled _, err = manager.DeterminePeerV6(account, peer1) + require.NoError(t, err, "unable to determine effective peer IPv6 status") _, err = manager.DeterminePeerV6(account, peer2) + require.NoError(t, err, "unable to determine effective peer IPv6 status") require.Nil(t, peer1.IP6, "peer1 IPv6 address is not nil even though it is force disabled.") require.Nil(t, peer2.IP6, "peer2 IPv6 address is not nil even though it is force disabled and unsupported.") peer1.V6Setting = nbpeer.V6Enabled peer2.V6Setting = nbpeer.V6Enabled _, err = manager.DeterminePeerV6(account, peer1) + require.NoError(t, err, "unable to determine effective peer IPv6 status") _, err = manager.DeterminePeerV6(account, peer2) + require.NoError(t, err, "unable to determine effective peer IPv6 status") require.NotNil(t, peer1.IP6, "peer1 IPv6 address is nil even though it is force enabled.") require.Nil(t, peer2.IP6, "peer2 IPv6 address is not nil even though it is unsupported.") @@ -229,7 +233,7 @@ func TestDefaultAccountManager_DeterminePeerV6(t *testing.T) { require.True(t, route.Enabled, "created IPv6 test route should be enabled") peer1.V6Setting = nbpeer.V6Disabled - peer1, err = manager.UpdatePeer(account.Id, userID, peer1) + _, err = manager.UpdatePeer(account.Id, userID, peer1) require.NoError(t, err, "unable to update peer") account, err = manager.Store.GetAccount(account.Id) From 45480697c763e1e0f8419f69cca2d13adba3523f Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Mon, 11 Mar 2024 03:10:03 +0100 Subject: [PATCH 40/42] Fix small test suite issues --- management/server/group_test.go | 16 +++++++++------- management/server/peer_test.go | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/management/server/group_test.go b/management/server/group_test.go index 035fbb564ac..efbfd5c6af4 100644 --- a/management/server/group_test.go +++ b/management/server/group_test.go @@ -161,14 +161,16 @@ func TestDefaultAccountManager_GroupIPv6Consistency(t *testing.T) { require.NotNil(t, account.Peers[peer2Id].IP6, "peer2 should have an IPv6 address as it is a member of the IPv6-enabled group.") // Force disable IPv6. - account.GetPeer(peer1Id).V6Setting = nbpeer.V6Disabled - account.GetPeer(peer2Id).V6Setting = nbpeer.V6Disabled - account.UpdatePeer(account.Peers[peer1Id]) - account.UpdatePeer(account.Peers[peer2Id]) - err = am.Store.SaveAccount(account) - require.NoError(t, err, "unable to update account") + peer1 := account.GetPeer(peer1Id) + peer2 := account.GetPeer(peer2Id) + peer1.V6Setting = nbpeer.V6Disabled + peer2.V6Setting = nbpeer.V6Disabled + _, err = am.UpdatePeer(account.Id, groupAdminUserID, peer1) + require.NoError(t, err, "unable to update peer1") + _, err = am.UpdatePeer(account.Id, groupAdminUserID, peer2) + require.NoError(t, err, "unable to update peer2") account, err = am.Store.GetAccount(account.Id) - require.NoError(t, err, "unable to update account") + require.NoError(t, err, "unable to fetch updated account") group = account.GetGroup("grp-for-ipv6") require.Nil(t, account.GetPeer(peer1Id).IP6, "peer1 should not have an IPv6 address as it is force disabled.") require.Nil(t, account.GetPeer(peer2Id).IP6, "peer2 should not have an IPv6 address as it is force disabled.") diff --git a/management/server/peer_test.go b/management/server/peer_test.go index 6a50378f74a..8ef79bf8736 100644 --- a/management/server/peer_test.go +++ b/management/server/peer_test.go @@ -221,7 +221,7 @@ func TestDefaultAccountManager_DeterminePeerV6(t *testing.T) { _, err = manager.DeterminePeerV6(account, peer1) require.NoError(t, err, "unable to determine effective peer IPv6 status") _, err = manager.DeterminePeerV6(account, peer2) - require.NoError(t, err, "unable to determine effective peer IPv6 status") + require.Error(t, err, "determining peer2 IPv6 address should fail as it is force enabled, but unsupported.") require.NotNil(t, peer1.IP6, "peer1 IPv6 address is nil even though it is force enabled.") require.Nil(t, peer2.IP6, "peer2 IPv6 address is not nil even though it is unsupported.") From 09799d403318595c251311d601e2deee647b00fe Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Sun, 2 Jun 2024 21:59:18 +0200 Subject: [PATCH 41/42] Fix linter issues and builds on macOS and Windows again --- client/internal/routemanager/server_nonandroid.go | 3 +++ client/internal/routemanager/systemops_linux.go | 6 ++++++ client/internal/routemanager/systemops_nonlinux.go | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/client/internal/routemanager/server_nonandroid.go b/client/internal/routemanager/server_nonandroid.go index d988f1d25cf..bd643357b50 100644 --- a/client/internal/routemanager/server_nonandroid.go +++ b/client/internal/routemanager/server_nonandroid.go @@ -164,6 +164,9 @@ func (m *defaultServerRouter) cleanUp() { routingAddress = m.wgInterface.Address6().Masked().String() } routerPair, err := routeToRouterPair(routingAddress, r) + if err != nil { + log.Errorf("parse prefix: %v", err) + } err = m.firewall.RemoveRoutingRules(routerPair) if err != nil { diff --git a/client/internal/routemanager/systemops_linux.go b/client/internal/routemanager/systemops_linux.go index e99cc398e48..36ca7b8f513 100644 --- a/client/internal/routemanager/systemops_linux.go +++ b/client/internal/routemanager/systemops_linux.go @@ -270,6 +270,9 @@ func addRoute(prefix netip.Prefix, addr netip.Addr, intf *net.Interface, tableID // addUnreachableRoute adds an unreachable route for the specified IP family and routing table. // ipFamily should be netlink.FAMILY_V4 for IPv4 or netlink.FAMILY_V6 for IPv6. // tableID specifies the routing table to which the unreachable route will be added. +// TODO should this be kept in for future use? If so, the linter needs to be told that this unreachable function should +// +// be kept func addUnreachableRoute(prefix netip.Prefix, tableID int) error { _, ipNet, err := net.ParseCIDR(prefix.String()) if err != nil { @@ -290,6 +293,9 @@ func addUnreachableRoute(prefix netip.Prefix, tableID int) error { return nil } +// TODO should this be kept in for future use? If so, the linter needs to be told that this unreachable function should +// +// be kept func removeUnreachableRoute(prefix netip.Prefix, tableID int) error { _, ipNet, err := net.ParseCIDR(prefix.String()) if err != nil { diff --git a/client/internal/routemanager/systemops_nonlinux.go b/client/internal/routemanager/systemops_nonlinux.go index 91879790a1f..f3b4306facb 100644 --- a/client/internal/routemanager/systemops_nonlinux.go +++ b/client/internal/routemanager/systemops_nonlinux.go @@ -10,7 +10,7 @@ import ( log "github.com/sirupsen/logrus" ) -func enableIPForwarding() error { +func enableIPForwarding(includeV6 bool) error { log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS) return nil } From d8c48da40e192835e4306d09891dc260448e21e1 Mon Sep 17 00:00:00 2001 From: Hugo Hakim Damer Date: Sat, 8 Jun 2024 22:41:57 +0200 Subject: [PATCH 42/42] fix builds for iOS because of IPv6 breakage --- client/internal/routemanager/systemops_ios.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/internal/routemanager/systemops_ios.go b/client/internal/routemanager/systemops_ios.go index 4d23d39100e..1aede183b66 100644 --- a/client/internal/routemanager/systemops_ios.go +++ b/client/internal/routemanager/systemops_ios.go @@ -19,7 +19,7 @@ func cleanupRouting() error { return nil } -func enableIPForwarding() error { +func enableIPForwarding(includeV6 bool) error { log.Infof("Enable IP forwarding is not implemented on %s", runtime.GOOS) return nil }