diff --git a/cmd/antrea-agent/agent.go b/cmd/antrea-agent/agent.go index 60bd61503aa..3e705eeaf5a 100644 --- a/cmd/antrea-agent/agent.go +++ b/cmd/antrea-agent/agent.go @@ -800,10 +800,6 @@ func run(o *Options) error { go memberlistCluster.Run(stopCh) } - if features.DefaultFeatureGate.Enabled(features.ServiceExternalIP) { - go externalIPController.Run(stopCh) - } - if features.DefaultFeatureGate.Enabled(features.Traceflow) { go traceflowController.Run(stopCh) } @@ -827,9 +823,6 @@ func run(o *Options) error { } go networkPolicyController.Run(stopCh) - if o.enableEgress { - go egressController.Run(stopCh) - } var mcastController *multicast.Controller if multicastEnabled { @@ -999,6 +992,14 @@ func run(o *Options) error { go nodeLatencyMonitor.Run(stopCh) } + if egressController != nil { + go egressController.Run(stopCh) + } + + if externalIPController != nil { + go externalIPController.Run(stopCh) + } + <-stopCh klog.InfoS("Stopping Antrea Agent") return nil diff --git a/go.mod b/go.mod index 81f5d308e5d..15f8976ef0e 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/lithammer/dedent v1.1.0 github.com/mdlayher/arp v0.0.0-20220221190821-c37aaafac7f9 github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 - github.com/mdlayher/ndp v0.8.0 + github.com/mdlayher/ndp v1.1.0 github.com/mdlayher/packet v1.1.2 github.com/miekg/dns v1.1.62 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 @@ -214,7 +214,6 @@ require ( github.com/vishvananda/netns v0.0.4 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect - gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f // indirect go.etcd.io/etcd/api/v3 v3.5.14 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect go.etcd.io/etcd/client/v3 v3.5.14 // indirect @@ -251,3 +250,6 @@ require ( sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) + +// remove this when https://github.com/mdlayher/ndp/pull/32 gets merged +replace github.com/mdlayher/ndp => github.com/xliuxu/ndp v0.0.0-20240926134643-8cf547505092 diff --git a/go.sum b/go.sum index b114becc5e3..3255133f904 100644 --- a/go.sum +++ b/go.sum @@ -349,7 +349,6 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -529,8 +528,6 @@ github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHI github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo= github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0= github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc= -github.com/mdlayher/ndp v0.8.0 h1:oVCl5JZSzT/YJE6cJd7EnNDWmX1fl4hJV0S/UCBNoHE= -github.com/mdlayher/ndp v0.8.0/go.mod h1:32w/5dDZWVSEOxyniAgKK4d7dHTuO6TCxWmUznQe3f8= github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= @@ -768,12 +765,12 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/xliuxu/ndp v0.0.0-20240926134643-8cf547505092 h1:1sBcuJrdQq9bawMA4Jm58h+cwefaV5ZIx5r50T/ZgTk= +github.com/xliuxu/ndp v0.0.0-20240926134643-8cf547505092/go.mod h1:FmgESgemgjl38vuOIyAHWUUL6vQKA/pQNkvXdWsdQFM= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f h1:Wku8eEdeJqIOFHtrfkYUByc4bCaTeA6fL0UJgfEiFMI= -gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f/go.mod h1:Tiuhl+njh/JIg0uS/sOJVYi0x2HEa5rc1OAaVsb5tAs= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= @@ -888,7 +885,6 @@ golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -916,7 +912,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -953,7 +948,6 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602100848-8d3cce7afc34/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/pkg/agent/ipassigner/ip_assigner_linux.go b/pkg/agent/ipassigner/ip_assigner_linux.go index 6256bae61b2..63cb3d5cb3a 100644 --- a/pkg/agent/ipassigner/ip_assigner_linux.go +++ b/pkg/agent/ipassigner/ip_assigner_linux.go @@ -242,17 +242,11 @@ func NewIPAssigner(nodeTransportInterface string, dummyDeviceName string) (IPAss return nil, err } if dummyDeviceName == "" || arpIgnore > 0 { - a.defaultAssignee.arpResponder, err = responder.NewARPResponder(externalInterface) - if err != nil { - return nil, fmt.Errorf("failed to create ARP responder for link %s: %v", externalInterface.Name, err) - } + a.defaultAssignee.arpResponder = responder.NewARPResponder(externalInterface.Name) } } if ipv6 != nil { - a.defaultAssignee.ndpResponder, err = responder.NewNDPResponder(externalInterface) - if err != nil { - return nil, fmt.Errorf("failed to create NDP responder for link %s: %v", externalInterface.Name, err) - } + a.defaultAssignee.ndpResponder = responder.NewNDPResponder(externalInterface.Name) } if dummyDeviceName != "" { a.defaultAssignee.link, err = ensureDummyDevice(dummyDeviceName) diff --git a/pkg/agent/ipassigner/responder/arp_responder.go b/pkg/agent/ipassigner/responder/arp_responder.go index a2cef75a9bf..e68a20e1cf1 100644 --- a/pkg/agent/ipassigner/responder/arp_responder.go +++ b/pkg/agent/ipassigner/responder/arp_responder.go @@ -18,36 +18,26 @@ import ( "fmt" "net" "sync" + "time" "github.com/mdlayher/arp" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/klog/v2" utilnet "k8s.io/utils/net" ) type arpResponder struct { - iface *net.Interface - conn *arp.Client + once sync.Once + ifaceName string assignedIPs sets.Set[string] mutex sync.Mutex } var _ Responder = (*arpResponder)(nil) -func NewARPResponder(iface *net.Interface) (*arpResponder, error) { - conn, err := arp.Dial(iface) - if err != nil { - return nil, fmt.Errorf("creating ARP responder for %q: %s", iface.Name, err) - } - return &arpResponder{ - iface: iface, - conn: conn, - assignedIPs: sets.New[string](), - }, nil -} - func (r *arpResponder) InterfaceName() string { - return r.iface.Name + return r.ifaceName } func (r *arpResponder) AddIP(ip net.IP) error { @@ -55,7 +45,7 @@ func (r *arpResponder) AddIP(ip net.IP) error { return fmt.Errorf("only IPv4 is supported") } if r.addIP(ip) { - klog.InfoS("Assigned IP to ARP responder", "ip", ip, "interface", r.iface.Name) + klog.InfoS("Assigned IP to ARP responder", "ip", ip, "interface", r.ifaceName) } return nil } @@ -65,13 +55,13 @@ func (r *arpResponder) RemoveIP(ip net.IP) error { return fmt.Errorf("only IPv4 is supported") } if r.deleteIP(ip) { - klog.InfoS("Removed IP from ARP responder", "ip", ip, "interface", r.iface.Name) + klog.InfoS("Removed IP from ARP responder", "ip", ip, "interface", r.ifaceName) } return nil } -func (r *arpResponder) handleARPRequest() error { - pkt, _, err := r.conn.Read() +func (r *arpResponder) handleARPRequest(client *arp.Client, iface *net.Interface) error { + pkt, _, err := client.Read() if err != nil { return err } @@ -79,26 +69,46 @@ func (r *arpResponder) handleARPRequest() error { return nil } if !r.isIPAssigned(pkt.TargetIP) { - klog.V(4).InfoS("Ignored ARP request", "ip", pkt.TargetIP, "interface", r.iface.Name) + klog.V(4).InfoS("Ignored ARP request", "ip", pkt.TargetIP, "interface", r.ifaceName) return nil } - if err := r.conn.Reply(pkt, r.iface.HardwareAddr, pkt.TargetIP); err != nil { + if err := client.Reply(pkt, iface.HardwareAddr, pkt.TargetIP); err != nil { return fmt.Errorf("failed to reply ARP packet for IP %s: %v", pkt.TargetIP, err) } - klog.V(4).InfoS("Sent ARP response", "ip", pkt.TargetIP, "interface", r.iface.Name) + klog.V(4).InfoS("Sent ARP response", "ip", pkt.TargetIP, "interface", r.ifaceName) return nil } func (r *arpResponder) Run(stopCh <-chan struct{}) { + r.once.Do(func() { + wait.NonSlidingUntil(func() { + r.dialAndHandleRequests(stopCh) + }, time.Second, stopCh) + }) + <-stopCh +} + +func (r *arpResponder) dialAndHandleRequests(stopCh <-chan struct{}) { + transportInterface, err := net.InterfaceByName(r.ifaceName) + if err != nil { + klog.ErrorS(err, "Failed to get interface by name", "deviceName", r.ifaceName) + return + } + client, err := arp.Dial(transportInterface) + if err != nil { + klog.ErrorS(err, "Failed to dial ARP client", "deviceName", r.ifaceName) + return + } + klog.InfoS("ARP responder started", "interface", transportInterface.Name, "index", transportInterface.Index) for { select { case <-stopCh: - r.conn.Close() + client.Close() return default: - err := r.handleARPRequest() + err := r.handleARPRequest(client, transportInterface) if err != nil { - klog.ErrorS(err, "Failed to handle ARP request", "deviceName", r.iface.Name) + klog.ErrorS(err, "Failed to handle ARP request", "deviceName", r.ifaceName) } } } diff --git a/pkg/agent/ipassigner/responder/arp_responder_test.go b/pkg/agent/ipassigner/responder/arp_responder_test.go index fcb05a8bb9a..77c204a06f5 100644 --- a/pkg/agent/ipassigner/responder/arp_responder_test.go +++ b/pkg/agent/ipassigner/responder/arp_responder_test.go @@ -103,14 +103,13 @@ func TestARPResponder_HandleARPRequest(t *testing.T) { assignedIPs.Insert(ip.String()) } r := arpResponder{ - iface: localIface, - conn: localARPClient, + ifaceName: localIface.Name, assignedIPs: sets.New[string](), } for _, ip := range tt.assignedIPs { r.AddIP(ip) } - err = r.handleARPRequest() + err = r.handleARPRequest(localARPClient, localIface) require.NoError(t, err) // We cannot use remoteARPClient.ReadFrom as it is blocking. replyB, addr, err := remoteConn.Receive() @@ -159,7 +158,7 @@ func Test_arpResponder_addIP(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &arpResponder{ - iface: iface, + ifaceName: iface.Name, assignedIPs: tt.assignedIPs, } err := r.AddIP(tt.ip) @@ -207,7 +206,7 @@ func Test_arpResponder_removeIP(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &arpResponder{ - iface: iface, + ifaceName: iface.Name, assignedIPs: tt.assignedIPs, } err := r.RemoveIP(tt.ip) diff --git a/pkg/agent/ipassigner/responder/factory.go b/pkg/agent/ipassigner/responder/factory.go new file mode 100644 index 00000000000..6f75ae80748 --- /dev/null +++ b/pkg/agent/ipassigner/responder/factory.go @@ -0,0 +1,62 @@ +// Copyright 2024 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package responder + +import ( + "net/netip" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog/v2" +) + +var ( + // map of transportInterfaceName to ARP responder + arpResponders = make(map[string]*arpResponder) + // map of transportInterfaceName to NDP responder + ndpResponders = make(map[string]*ndpResponder) +) + +// NewARPResponder creates a new ARP responder if it does not exist for the given transportInterfaceName. +// This function is not thread-safe. +func NewARPResponder(transportInterfaceName string) *arpResponder { + if responder, ok := arpResponders[transportInterfaceName]; ok { + klog.InfoS("ARP responder already exists", "interface", transportInterfaceName) + return responder + } + a := &arpResponder{ + ifaceName: transportInterfaceName, + assignedIPs: sets.New[string](), + } + klog.InfoS("Created new ARP responder", "interface", transportInterfaceName) + arpResponders[transportInterfaceName] = a + return a +} + +// NewNDPResponder creates a new NDP responder if it does not exist for the given transportInterfaceName. +// This function is not thread-safe. +func NewNDPResponder(transportInterfaceName string) *ndpResponder { + if responder, ok := ndpResponders[transportInterfaceName]; ok { + klog.InfoS("NDP responder already exists", "interface", transportInterfaceName) + return responder + } + n := &ndpResponder{ + ifaceName: transportInterfaceName, + multicastGroups: make(map[netip.Addr]int), + assignedIPs: sets.New[netip.Addr](), + } + klog.InfoS("Created new NDP responder", "interface", transportInterfaceName) + ndpResponders[transportInterfaceName] = n + return n +} diff --git a/pkg/agent/ipassigner/responder/interface.go b/pkg/agent/ipassigner/responder/interface.go index 4e8d7917d40..e4f8fbe6294 100644 --- a/pkg/agent/ipassigner/responder/interface.go +++ b/pkg/agent/ipassigner/responder/interface.go @@ -14,7 +14,9 @@ package responder -import "net" +import ( + "net" +) // Responder is an interface to handle ARP (IPv4)/NS (IPv6) queries using raw sockets. type Responder interface { diff --git a/pkg/agent/ipassigner/responder/ndp_responder.go b/pkg/agent/ipassigner/responder/ndp_responder.go index 103843fecdf..262fd7bfd3c 100644 --- a/pkg/agent/ipassigner/responder/ndp_responder.go +++ b/pkg/agent/ipassigner/responder/ndp_responder.go @@ -17,64 +17,56 @@ package responder import ( "fmt" "net" + "net/netip" + "strconv" "sync" + "time" "github.com/mdlayher/ndp" "golang.org/x/net/ipv6" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/klog/v2" utilnet "k8s.io/utils/net" ) -const ( - solicitedNodeMulticastAddressPrefix = "ff02::1:ff00:0" +var ( + solicitedNodeMulticastAddressPrefix = netip.MustParseAddr("ff02::1:ff00:0") ) type ndpConn interface { - WriteTo(message ndp.Message, cm *ipv6.ControlMessage, dstIP net.IP) error - ReadFrom() (ndp.Message, *ipv6.ControlMessage, net.IP, error) - JoinGroup(net.IP) error - LeaveGroup(net.IP) error + WriteTo(message ndp.Message, cm *ipv6.ControlMessage, dstIP netip.Addr) error + ReadFrom() (ndp.Message, *ipv6.ControlMessage, netip.Addr, error) + JoinGroup(netip.Addr) error + LeaveGroup(netip.Addr) error Close() error } type ndpResponder struct { - iface *net.Interface + once sync.Once + ifaceName string conn ndpConn - assignedIPs sets.Set[string] - multicastGroups map[int]int + assignedIPs sets.Set[netip.Addr] + multicastGroups map[netip.Addr]int mutex sync.Mutex } var _ Responder = (*ndpResponder)(nil) -func parseIPv6SolicitedNodeMulticastAddress(ip net.IP) (net.IP, int) { - group := net.ParseIP(solicitedNodeMulticastAddressPrefix) +func parseIPv6SolicitedNodeMulticastAddress(ip netip.Addr) netip.Addr { + target := ip.As16() + prefix := solicitedNodeMulticastAddressPrefix.As16() // copy lower 24 bits - copy(group[13:], ip[13:]) - key := int(group[13])<<16 | int(group[14])<<8 | int(group[15]) - return group, key -} - -func NewNDPResponder(iface *net.Interface) (*ndpResponder, error) { - conn, _, err := ndp.Listen(iface, ndp.LinkLocal) - if err != nil { - return nil, err - } - return &ndpResponder{ - iface: iface, - conn: conn, - multicastGroups: make(map[int]int), - assignedIPs: sets.New[string](), - }, nil + copy(prefix[13:], target[13:]) + return netip.AddrFrom16(prefix) } func (r *ndpResponder) InterfaceName() string { - return r.iface.Name + return r.ifaceName } -func (r *ndpResponder) handleNeighborSolicitation() error { - pkt, _, srcIP, err := r.conn.ReadFrom() +func (r *ndpResponder) handleNeighborSolicitation(conn ndpConn, intf *net.Interface) error { + pkt, _, srcIP, err := conn.ReadFrom() if err != nil { return err } @@ -98,7 +90,7 @@ func (r *ndpResponder) handleNeighborSolicitation() error { return nil } if !r.isIPAssigned(ns.TargetAddress) { - klog.V(4).InfoS("Ignored Neighbor Solicitation", "ip", ns.TargetAddress.String(), "interface", r.iface.Name) + klog.V(4).InfoS("Ignored Neighbor Solicitation", "ip", ns.TargetAddress, "interface", r.ifaceName) return nil } na := &ndp.NeighborAdvertisement{ @@ -108,27 +100,84 @@ func (r *ndpResponder) handleNeighborSolicitation() error { Options: []ndp.Option{ &ndp.LinkLayerAddress{ Direction: ndp.Target, - Addr: r.iface.HardwareAddr, + Addr: intf.HardwareAddr, }, }, } - if err := r.conn.WriteTo(na, nil, srcIP); err != nil { + if err := conn.WriteTo(na, nil, srcIP); err != nil { return err } - klog.V(4).InfoS("Sent Neighbor Advertisement", "ip", ns.TargetAddress.String(), "interface", r.iface.Name) + klog.V(4).InfoS("Sent Neighbor Advertisement", "ip", ns.TargetAddress.String(), "interface", r.ifaceName) return nil } func (r *ndpResponder) Run(stopCh <-chan struct{}) { + r.once.Do(func() { + wait.NonSlidingUntil(func() { + r.dialAndHandleRequests(stopCh) + }, time.Second, stopCh) + }) + <-stopCh +} + +func (r *ndpResponder) dialAndHandleRequests(endCh <-chan struct{}) { + intf, err := net.InterfaceByName(r.ifaceName) + if err != nil { + klog.ErrorS(err, "Failed to get interface", "interface", r.ifaceName) + return + } + addrs, err := intf.Addrs() + if err != nil { + klog.ErrorS(err, "Failed to get addresses for interface", "interface", r.ifaceName) + return + } + var ip netip.Addr + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok { + if utilnet.IsIPv6(ipnet.IP) && ipnet.IP.IsLinkLocalUnicast() { + ip, _ = netip.AddrFromSlice(ipnet.IP) + break + } + } + } + if ip.IsUnspecified() { + klog.InfoS("Interface does not have a link-local IPv6 address yet", "interface", r.ifaceName) + return + } + // Specify the zone index to bind to the correct interface. + // The zone index is cached https://github.com/golang/go/blob/go1.23.1/src/net/interface.go#L192 + // and may result in binding on the wrong interface if using the interface name as zone in secondary network use cases. + bindAddr := ip.WithZone(strconv.Itoa(intf.Index)) + + // It may take time for the interface to be ready for socket binding. For example, IPv6 introduces Duplicate Address Detection, + // which may take time to allow the address to be used for socket binding. EADDRNOTAVAIL (bind: cannot assign requested address) + // may be returned for such cases. + klog.InfoS("Binding NDP responder on address", "address", bindAddr.String(), "interface", r.ifaceName) + conn, _, err := ndp.Listen(intf, ndp.Addr(bindAddr.String())) + if err != nil { + klog.ErrorS(err, "Failed to create NDP responder", "interface", r.ifaceName) + return + } + + r.mutex.Lock() + r.conn = conn + for ip := range r.assignedIPs { + if err := r.joinMulticastGroup(ip); err != nil { + klog.ErrorS(err, "Failed to join multicast group", "ip", ip, "interface", r.ifaceName) + } + } + r.mutex.Unlock() + + klog.InfoS("NDP responder started", "interface", intf.Name, "index", intf.Index) for { select { - case <-stopCh: - r.conn.Close() + case <-endCh: + conn.Close() return default: - err := r.handleNeighborSolicitation() + err := r.handleNeighborSolicitation(conn, intf) if err != nil { - klog.ErrorS(err, "Failed to handle Neighbor Solicitation", "deviceName", r.iface.Name) + klog.ErrorS(err, "Failed to handle Neighbor Solicitation", "deviceName", r.ifaceName) } } } @@ -138,26 +187,55 @@ func (r *ndpResponder) AddIP(ip net.IP) error { if !utilnet.IsIPv6(ip) { return fmt.Errorf("only IPv6 is supported") } - if r.isIPAssigned(ip) { + + target, _ := netip.AddrFromSlice(ip) + if r.isIPAssigned(target) { return nil } - group, key := parseIPv6SolicitedNodeMulticastAddress(ip) - if err := func() error { - r.mutex.Lock() - defer r.mutex.Unlock() - if r.multicastGroups[key] == 0 { - if err := r.conn.JoinGroup(group); err != nil { - return fmt.Errorf("joining solicited-node multicast group %s for %q failed: %v", group, ip, err) - } - klog.InfoS("Joined solicited-node multicast group", "group", group, "interface", r.iface.Name) - } - klog.InfoS("Assigned IP to NDP responder", "ip", ip, "interface", r.iface.Name) - r.multicastGroups[key]++ - r.assignedIPs.Insert(ip.String()) - return nil - }(); err != nil { + + r.mutex.Lock() + if err := r.joinMulticastGroup(target); err != nil { return err } + r.assignedIPs.Insert(target) + r.mutex.Unlock() + + return nil +} + +func (r *ndpResponder) joinMulticastGroup(ip netip.Addr) error { + if r.conn == nil { + klog.InfoS("NDP responder is not initialized") + return nil + } + group := parseIPv6SolicitedNodeMulticastAddress(ip) + if r.multicastGroups[group] > 0 { + r.multicastGroups[group]++ + return nil + } + if err := r.conn.JoinGroup(group); err != nil { + return fmt.Errorf("joining multicast group %s failed: %v", group, err) + } + klog.InfoS("Joined multicast group", "group", group, "interface", r.ifaceName) + r.multicastGroups[group]++ + return nil +} + +func (r *ndpResponder) leaveMulticastGroup(ip netip.Addr) error { + if r.conn == nil { + klog.InfoS("NDP responder is not initialized") + return nil + } + group := parseIPv6SolicitedNodeMulticastAddress(ip) + if r.multicastGroups[group] > 1 { + r.multicastGroups[group]-- + return nil + } + if err := r.conn.LeaveGroup(group); err != nil { + return fmt.Errorf("leaving multicast group %s failed: %v", group, err) + } + klog.InfoS("Left multicast group", "group", group, "interface", r.ifaceName) + delete(r.multicastGroups, group) return nil } @@ -165,28 +243,24 @@ func (r *ndpResponder) RemoveIP(ip net.IP) error { if !utilnet.IsIPv6(ip) { return fmt.Errorf("only IPv6 is supported") } - r.mutex.Lock() - defer r.mutex.Unlock() - if !r.assignedIPs.Has(ip.String()) { + target, _ := netip.AddrFromSlice(ip) + if !r.isIPAssigned(target) { return nil } - group, key := parseIPv6SolicitedNodeMulticastAddress(ip) - if r.multicastGroups[key] == 1 { - if err := r.conn.LeaveGroup(group); err != nil { - return fmt.Errorf("leaving solicited-node multicast group %s for %q failed: %v", group, ip, err) - } - klog.InfoS("Left solicited-node multicast group", "group", group, "interface", r.iface.Name) - delete(r.multicastGroups, key) - } else { - r.multicastGroups[key]-- + + r.mutex.Lock() + if err := r.leaveMulticastGroup(target); err != nil { + return err } - r.assignedIPs.Delete(ip.String()) - klog.InfoS("Removed IP from NDP responder", "ip", ip, "interface", r.iface.Name) + r.assignedIPs.Delete(target) + r.mutex.Unlock() + + klog.InfoS("Removed IP from NDP responder", "ip", ip, "interface", r.ifaceName) return nil } -func (r *ndpResponder) isIPAssigned(ip net.IP) bool { +func (r *ndpResponder) isIPAssigned(ip netip.Addr) bool { r.mutex.Lock() defer r.mutex.Unlock() - return r.assignedIPs.Has(ip.String()) + return r.assignedIPs.Has(ip) } diff --git a/pkg/agent/ipassigner/responder/ndp_responder_test.go b/pkg/agent/ipassigner/responder/ndp_responder_test.go index 5d2f66d4c14..a5410454945 100644 --- a/pkg/agent/ipassigner/responder/ndp_responder_test.go +++ b/pkg/agent/ipassigner/responder/ndp_responder_test.go @@ -16,7 +16,7 @@ package responder import ( "bytes" - "net" + "net/netip" "testing" "github.com/mdlayher/ndp" @@ -26,17 +26,17 @@ import ( ) type fakeNDPConn struct { - readFrom func() (ndp.Message, *ipv6.ControlMessage, net.IP, error) - writeTo func(ndp.Message, *ipv6.ControlMessage, net.IP) error - joinGroup func(ip net.IP) error - leaveGroup func(ip net.IP) error + readFrom func() (ndp.Message, *ipv6.ControlMessage, netip.Addr, error) + writeTo func(ndp.Message, *ipv6.ControlMessage, netip.Addr) error + joinGroup func(ip netip.Addr) error + leaveGroup func(ip netip.Addr) error } -func (c *fakeNDPConn) ReadFrom() (ndp.Message, *ipv6.ControlMessage, net.IP, error) { +func (c *fakeNDPConn) ReadFrom() (ndp.Message, *ipv6.ControlMessage, netip.Addr, error) { return c.readFrom() } -func (c *fakeNDPConn) WriteTo(message ndp.Message, cm *ipv6.ControlMessage, dstIP net.IP) error { +func (c *fakeNDPConn) WriteTo(message ndp.Message, cm *ipv6.ControlMessage, dstIP netip.Addr) error { return c.writeTo(message, cm, dstIP) } @@ -44,11 +44,11 @@ func (c *fakeNDPConn) Close() error { return nil } -func (c *fakeNDPConn) JoinGroup(ip net.IP) error { +func (c *fakeNDPConn) JoinGroup(ip netip.Addr) error { return c.joinGroup(ip) } -func (c *fakeNDPConn) LeaveGroup(ip net.IP) error { +func (c *fakeNDPConn) LeaveGroup(ip netip.Addr) error { return c.leaveGroup(ip) } @@ -59,8 +59,8 @@ func TestNDPResponder_handleNeighborSolicitation(t *testing.T) { tests := []struct { name string requestMessage []byte - requestIP net.IP - assignedIPs []net.IP + requestIP netip.Addr + assignedIPs []netip.Addr expectError bool expectedReply []byte }{ @@ -76,10 +76,10 @@ func TestNDPResponder_handleNeighborSolicitation(t *testing.T) { 0x01, // length (units of 8 octets including type and length fields) 0x00, 0x11, 0x22, 0x33, 0x44, 0x66, // hardware address }, - requestIP: net.ParseIP("fe80::c1"), - assignedIPs: []net.IP{ - net.ParseIP("fe80::a1"), - net.ParseIP("fe80::a2"), + requestIP: netip.MustParseAddr("fe80::c1"), + assignedIPs: []netip.Addr{ + netip.MustParseAddr("fe80::a1"), + netip.MustParseAddr("fe80::a2"), }, expectError: false, expectedReply: []byte{ @@ -105,10 +105,10 @@ func TestNDPResponder_handleNeighborSolicitation(t *testing.T) { 0x01, // length (units of 8 octets including type and length fields) 0x00, 0x11, 0x22, 0x33, 0x44, 0x66, // hardware address }, - requestIP: net.ParseIP("fe80::c1"), - assignedIPs: []net.IP{ - net.ParseIP("fe80::a1"), - net.ParseIP("fe80::a2"), + requestIP: netip.MustParseAddr("fe80::c1"), + assignedIPs: []netip.Addr{ + netip.MustParseAddr("fe80::a1"), + netip.MustParseAddr("fe80::a2"), }, expectError: false, expectedReply: nil, @@ -118,13 +118,13 @@ func TestNDPResponder_handleNeighborSolicitation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { buffer := bytes.NewBuffer(nil) fakeConn := &fakeNDPConn{ - writeTo: func(msg ndp.Message, _ *ipv6.ControlMessage, _ net.IP) error { + writeTo: func(msg ndp.Message, _ *ipv6.ControlMessage, _ netip.Addr) error { bs, err := ndp.MarshalMessage(msg) assert.NoError(t, err) buffer.Write(bs) return nil }, - readFrom: func() (ndp.Message, *ipv6.ControlMessage, net.IP, error) { + readFrom: func() (ndp.Message, *ipv6.ControlMessage, netip.Addr, error) { msg, err := ndp.ParseMessage(tt.requestMessage) return msg, nil, tt.requestIP, err }, @@ -134,14 +134,14 @@ func TestNDPResponder_handleNeighborSolicitation(t *testing.T) { assignedIPs.Insert(ip.String()) } responder := &ndpResponder{ - iface: iface, + ifaceName: iface.Name, conn: fakeConn, - assignedIPs: sets.New[string](), + assignedIPs: sets.New[netip.Addr](), } for _, ip := range tt.assignedIPs { - responder.assignedIPs[ip.String()] = struct{}{} + responder.assignedIPs[ip] = struct{}{} } - err := responder.handleNeighborSolicitation() + err := responder.handleNeighborSolicitation(fakeConn, iface) if tt.expectError { assert.Error(t, err) } else { @@ -155,34 +155,30 @@ func TestNDPResponder_handleNeighborSolicitation(t *testing.T) { func Test_parseIPv6SolicitedNodeMulticastAddress(t *testing.T) { tests := []struct { name string - ip net.IP - expectedGroup net.IP + ip netip.Addr + expectedGroup netip.Addr expectedKey int }{ { name: "global unicast IPv6 address 1", - ip: net.ParseIP("2022:abcd::11:1111"), - expectedGroup: net.ParseIP("ff02::1:ff11:1111"), - expectedKey: 0x111111, + ip: netip.MustParseAddr("2022:abcd::11:1111"), + expectedGroup: netip.MustParseAddr("ff02::1:ff11:1111"), }, { name: "global unicast IPv6 address 2", - ip: net.ParseIP("2022:ffff::1234:5678"), - expectedGroup: net.ParseIP("ff02::1:ff34:5678"), - expectedKey: 0x345678, + ip: netip.MustParseAddr("2022:ffff::1234:5678"), + expectedGroup: netip.MustParseAddr("ff02::1:ff34:5678"), }, { name: "link-local unicast IPv6 address", - ip: net.ParseIP("fe80::1122:3344"), - expectedGroup: net.ParseIP("ff02::1:ff22:3344"), - expectedKey: 0x223344, + ip: netip.MustParseAddr("fe80::1122:3344"), + expectedGroup: netip.MustParseAddr("ff02::1:ff22:3344"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - group, key := parseIPv6SolicitedNodeMulticastAddress(tt.ip) + group := parseIPv6SolicitedNodeMulticastAddress(tt.ip) assert.Equal(t, tt.expectedGroup, group) - assert.Equal(t, tt.expectedKey, key) }) } } @@ -193,86 +189,86 @@ func Test_ndpResponder_addIP(t *testing.T) { tests := []struct { name string - ip net.IP + ip netip.Addr conn ndpConn - assignedIPs sets.Set[string] - multicastGroups map[int]int - expectedJoinedGroups []net.IP - expectedLeftGroups []net.IP - expectedMulticastGroups map[int]int - expectedAssigndIPs sets.Set[string] + assignedIPs sets.Set[netip.Addr] + multicastGroups map[netip.Addr]int + expectedJoinedGroups []netip.Addr + expectedLeftGroups []netip.Addr + expectedMulticastGroups map[netip.Addr]int + expectedAssigndIPs sets.Set[netip.Addr] expectedError bool }{ { name: "Add new IP from a new multicast group 1", - ip: net.ParseIP("2022::beaf"), - assignedIPs: sets.New[string](), - multicastGroups: map[int]int{}, - expectedJoinedGroups: []net.IP{net.ParseIP("ff02::1:ff00:beaf")}, + ip: netip.MustParseAddr("2022::beaf"), + assignedIPs: sets.New[netip.Addr](), + multicastGroups: map[netip.Addr]int{}, + expectedJoinedGroups: []netip.Addr{netip.MustParseAddr("ff02::1:ff00:beaf")}, expectedLeftGroups: nil, - expectedMulticastGroups: map[int]int{ - 0xbeaf: 1, + expectedMulticastGroups: map[netip.Addr]int{ + netip.MustParseAddr("ff02::1:ff00:beaf"): 1, }, - expectedAssigndIPs: sets.New[string]("2022::beaf"), + expectedAssigndIPs: sets.New[netip.Addr](netip.MustParseAddr("2022::beaf")), }, { name: "Add new IP from a new multicast group 2", - ip: net.ParseIP("2022::beaf:beaf"), - assignedIPs: sets.New[string](), - multicastGroups: map[int]int{}, - expectedJoinedGroups: []net.IP{net.ParseIP("ff02::1:ffaf:beaf")}, + ip: netip.MustParseAddr("2022::beaf:beaf"), + assignedIPs: sets.New[netip.Addr](), + multicastGroups: map[netip.Addr]int{}, + expectedJoinedGroups: []netip.Addr{netip.MustParseAddr("ff02::1:ffaf:beaf")}, expectedLeftGroups: nil, - expectedMulticastGroups: map[int]int{ - 0xafbeaf: 1, + expectedMulticastGroups: map[netip.Addr]int{ + netip.MustParseAddr("ff02::1:ffaf:beaf"): 1, }, - expectedAssigndIPs: sets.New[string]("2022::beaf:beaf"), + expectedAssigndIPs: sets.New[netip.Addr](netip.MustParseAddr("2022::beaf:beaf")), }, { name: "Add new IP from an existing multicast group", - ip: net.ParseIP("2021::beaf"), - assignedIPs: sets.New[string]("2022::beaf"), - multicastGroups: map[int]int{ - 0xbeaf: 1, + ip: netip.MustParseAddr("2021::beaf"), + assignedIPs: sets.New[netip.Addr](netip.MustParseAddr("2022::beaf")), + multicastGroups: map[netip.Addr]int{ + netip.MustParseAddr("ff02::1:ff00:beaf"): 1, }, expectedJoinedGroups: nil, expectedLeftGroups: nil, - expectedMulticastGroups: map[int]int{ - 0xbeaf: 2, + expectedMulticastGroups: map[netip.Addr]int{ + netip.MustParseAddr("ff02::1:ff00:beaf"): 2, }, - expectedAssigndIPs: sets.New[string]("2021::beaf", "2022::beaf"), + expectedAssigndIPs: sets.New[netip.Addr](netip.MustParseAddr("2021::beaf"), netip.MustParseAddr("2022::beaf")), }, { name: "Add invalid IP", - ip: net.ParseIP("1.2.3.4"), - assignedIPs: sets.New[string](), - multicastGroups: map[int]int{}, + ip: netip.MustParseAddr("1.2.3.4"), + assignedIPs: sets.New[netip.Addr](), + multicastGroups: map[netip.Addr]int{}, expectedError: true, - expectedMulticastGroups: map[int]int{}, - expectedAssigndIPs: sets.New[string](), + expectedMulticastGroups: map[netip.Addr]int{}, + expectedAssigndIPs: sets.New[netip.Addr](), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var joinedGroup, leftGroup []net.IP + var joinedGroup, leftGroup []netip.Addr r := &ndpResponder{ - iface: iface, + ifaceName: iface.Name, conn: &fakeNDPConn{ - joinGroup: func(ip net.IP) error { + joinGroup: func(ip netip.Addr) error { joinedGroup = append(joinedGroup, ip) return nil }, - leaveGroup: func(ip net.IP) error { + leaveGroup: func(ip netip.Addr) error { leftGroup = append(leftGroup, ip) return nil }, - writeTo: func(_ ndp.Message, _ *ipv6.ControlMessage, _ net.IP) error { + writeTo: func(_ ndp.Message, _ *ipv6.ControlMessage, _ netip.Addr) error { return nil }, }, assignedIPs: tt.assignedIPs, multicastGroups: tt.multicastGroups, } - err := r.AddIP(tt.ip) + err := r.AddIP(tt.ip.AsSlice()) if tt.expectedError { assert.Error(t, err) } else { @@ -292,82 +288,85 @@ func Test_ndpResponder_removeIP(t *testing.T) { tests := []struct { name string - ip net.IP + ip netip.Addr conn ndpConn - assignedIPs sets.Set[string] - multicastGroups map[int]int - expectedJoinedGroups []net.IP - expectedLeftGroups []net.IP - expectedMulticastGroups map[int]int - expectedAssigndIPs sets.Set[string] + assignedIPs sets.Set[netip.Addr] + multicastGroups map[netip.Addr]int + expectedJoinedGroups []netip.Addr + expectedLeftGroups []netip.Addr + expectedMulticastGroups map[netip.Addr]int + expectedAssigndIPs sets.Set[netip.Addr] expectedError bool }{ { name: "Remove IP and leave multicast group", - ip: net.ParseIP("2022::beaf"), - assignedIPs: sets.New[string]("2022::beaf"), - multicastGroups: map[int]int{ - 0xbeaf: 1, + ip: netip.MustParseAddr("2022::beaf"), + assignedIPs: sets.New[netip.Addr](netip.MustParseAddr("2022::beaf")), + multicastGroups: map[netip.Addr]int{ + netip.MustParseAddr("ff02::1:ff00:beaf"): 1, }, expectedJoinedGroups: nil, - expectedLeftGroups: []net.IP{net.ParseIP("ff02::1:ff00:beaf")}, - expectedMulticastGroups: map[int]int{}, - expectedAssigndIPs: sets.New[string](), + expectedLeftGroups: []netip.Addr{netip.MustParseAddr("ff02::1:ff00:beaf")}, + expectedMulticastGroups: map[netip.Addr]int{}, + expectedAssigndIPs: sets.New[netip.Addr](), }, { - name: "Remove IP and should not leave multicast group", - ip: net.ParseIP("2022::beaf"), - assignedIPs: sets.New[string]("2022::beaf", "2021::beaf"), - multicastGroups: map[int]int{ - 0xbeaf: 2, + name: "Remove IP and should not leave multicast group", + ip: netip.MustParseAddr("2022::beaf"), + assignedIPs: sets.New[netip.Addr]( + netip.MustParseAddr("2022::beaf"), + netip.MustParseAddr("2021::beaf"), + ), + multicastGroups: map[netip.Addr]int{ + netip.MustParseAddr("ff02::1:ff00:beaf"): 2, }, expectedJoinedGroups: nil, expectedLeftGroups: nil, - expectedMulticastGroups: map[int]int{ - 0xbeaf: 1, + expectedMulticastGroups: map[netip.Addr]int{ + netip.MustParseAddr("ff02::1:ff00:beaf"): 1, }, - expectedAssigndIPs: sets.New[string]("2021::beaf"), + expectedAssigndIPs: sets.New[netip.Addr](netip.MustParseAddr("2021::beaf")), }, { name: "Remove non-existent IP", - ip: net.ParseIP("2022::beaf"), - assignedIPs: sets.New[string]("2021::beaf"), - multicastGroups: map[int]int{ - 0xbeaf: 1, + ip: netip.MustParseAddr("2022::beaf"), + assignedIPs: sets.New[netip.Addr](netip.MustParseAddr("2021::beaf")), + multicastGroups: map[netip.Addr]int{ + netip.MustParseAddr("ff02::1:ff00:beaf"): 1, }, expectedJoinedGroups: nil, expectedLeftGroups: nil, - expectedMulticastGroups: map[int]int{ - 0xbeaf: 1, + expectedMulticastGroups: map[netip.Addr]int{ + netip.MustParseAddr("ff02::1:ff00:beaf"): 1, }, - expectedAssigndIPs: sets.New[string]("2021::beaf"), + expectedAssigndIPs: sets.New[netip.Addr](netip.MustParseAddr("2021::beaf")), }, { name: "Remove invalid IP", - ip: net.ParseIP("1.2.3.4"), - assignedIPs: sets.New[string]("2021::beaf"), - multicastGroups: map[int]int{ - 0xbeaf: 1, + ip: netip.MustParseAddr("1.2.3.4"), + assignedIPs: sets.New[netip.Addr](netip.MustParseAddr("2021::beaf")), + multicastGroups: map[netip.Addr]int{ + netip.MustParseAddr("ff02::1:ff00:beaf"): 1, }, expectedJoinedGroups: nil, expectedLeftGroups: nil, - expectedMulticastGroups: map[int]int{ - 0xbeaf: 1, + expectedMulticastGroups: map[netip.Addr]int{ + netip.MustParseAddr("ff02::1:ff00:beaf"): 1, }, - expectedAssigndIPs: sets.New[string]("2021::beaf"), + expectedAssigndIPs: sets.New[netip.Addr](netip.MustParseAddr("2021::beaf")), expectedError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var joinedGroup, leftGroup []net.IP + var joinedGroup, leftGroup []netip.Addr r := &ndpResponder{ - iface: iface, + ifaceName: iface.Name, conn: &fakeNDPConn{ - joinGroup: func(ip net.IP) error { + joinGroup: func(ip netip.Addr) error { joinedGroup = append(joinedGroup, ip) return nil }, - leaveGroup: func(ip net.IP) error { + leaveGroup: func(ip netip.Addr) error { leftGroup = append(leftGroup, ip) return nil }, @@ -375,7 +374,7 @@ func Test_ndpResponder_removeIP(t *testing.T) { assignedIPs: tt.assignedIPs, multicastGroups: tt.multicastGroups, } - err := r.RemoveIP(tt.ip) + err := r.RemoveIP(tt.ip.AsSlice()) if tt.expectedError { assert.Error(t, err) } else { diff --git a/pkg/agent/util/ndp/ndp.go b/pkg/agent/util/ndp/ndp.go index e4723ab113b..d6592d48a2a 100644 --- a/pkg/agent/util/ndp/ndp.go +++ b/pkg/agent/util/ndp/ndp.go @@ -17,10 +17,16 @@ package ndp import ( "fmt" "net" + "net/netip" "github.com/mdlayher/ndp" ) +var ( + // IPv6AllNodes is the IPv6 link-local multicast address for all nodes. + IPv6AllNodes = netip.MustParseAddr("ff02::1") +) + // GratuitousNDPOverIface sends a gratuitous NDP from 'iface' using 'srcIP' as the source IP. func GratuitousNDPOverIface(srcIP net.IP, iface *net.Interface) error { if srcIP.To4() != nil { @@ -32,10 +38,10 @@ func GratuitousNDPOverIface(srcIP net.IP, iface *net.Interface) error { return fmt.Errorf("failed to create NDP responder for %q: %s", iface.Name, err) } defer conn.Close() - + target, _ := netip.AddrFromSlice(srcIP) na := &ndp.NeighborAdvertisement{ Override: true, - TargetAddress: srcIP, + TargetAddress: target, Options: []ndp.Option{ &ndp.LinkLayerAddress{ Direction: ndp.Target, @@ -43,5 +49,5 @@ func GratuitousNDPOverIface(srcIP net.IP, iface *net.Interface) error { }, }, } - return conn.WriteTo(na, nil, net.IPv6linklocalallnodes) + return conn.WriteTo(na, nil, IPv6AllNodes) }