diff --git a/net.go b/net.go index 5fe3783..10e30a0 100644 --- a/net.go +++ b/net.go @@ -409,6 +409,28 @@ func (ifc *Interface) AddAddress(addr net.Addr) { ifc.addrs = append(ifc.addrs, addr) } +// RemoveAddress removes an address from the interface. +func (ifc *Interface) RemoveAddress(ip net.IP) bool { + for i, addr := range ifc.addrs { + var addrIP net.IP + switch a := addr.(type) { + case *net.IPNet: + addrIP = a.IP + case *net.IPAddr: + addrIP = a.IP + default: + continue + } + if addrIP.Equal(ip) { + ifc.addrs = append(ifc.addrs[:i], ifc.addrs[i+1:]...) + + return true + } + } + + return false +} + // Addrs returns a slice of configured addresses on the interface. func (ifc *Interface) Addrs() ([]net.Addr, error) { if len(ifc.addrs) == 0 { diff --git a/vnet/net.go b/vnet/net.go index 157c2b4..8fb5134 100644 --- a/vnet/net.go +++ b/vnet/net.go @@ -155,6 +155,49 @@ func (v *Net) setRouter(r *Router) error { return nil } +// AddAddress adds an address to an interface and registers it for routing. +// This method can be called before or after the router has started. +func (v *Net) AddAddress(ifName string, addr *net.IPNet) error { + v.mutex.Lock() + defer v.mutex.Unlock() + + ifc, err := v._getInterface(ifName) + if err != nil { + return err + } + ifc.AddAddress(addr) + + if v.router != nil { + v.router.mutex.Lock() + defer v.router.mutex.Unlock() + + return v.router.addIPToNIC(v, addr.IP) + } + + return nil +} + +// RemoveAddress removes an address from an interface and unregisters it from routing. +// This method can be called before or after the router has started. +func (v *Net) RemoveAddress(ifName string, ip net.IP) error { + v.mutex.Lock() + defer v.mutex.Unlock() + + ifc, err := v._getInterface(ifName) + if err != nil { + return err + } + ifc.RemoveAddress(ip) + + if v.router != nil { + v.router.mutex.Lock() + defer v.router.mutex.Unlock() + v.router.removeIPFromNIC(ip) + } + + return nil +} + func (v *Net) onInboundChunk(c Chunk) { v.mutex.Lock() defer v.mutex.Unlock() diff --git a/vnet/net_test.go b/vnet/net_test.go index 5a497a0..79c3104 100644 --- a/vnet/net_test.go +++ b/vnet/net_test.go @@ -807,4 +807,72 @@ func TestNetVirtual(t *testing.T) { //nolint:gocyclo,cyclop,maintidx assert.NoError(t, wan.Stop(), "should succeed") }) + + t.Run("AddAddress and RemoveAddress", func(t *testing.T) { + router, err := NewRouter(&RouterConfig{ + CIDR: "10.0.0.0/24", + LoggerFactory: loggerFactory, + }) + if !assert.NoError(t, err, "should succeed") { + return + } + + nw, err := NewNet(&NetConfig{}) + if !assert.NoError(t, err, "should succeed") { + return + } + + err = router.AddNet(nw) + assert.NoError(t, err, "should succeed") + + err = router.Start() + assert.NoError(t, err, "should succeed") + + // Add a new address dynamically. + newIP := net.ParseIP("10.0.0.100") + err = nw.AddAddress("eth0", &net.IPNet{ + IP: newIP, + Mask: net.CIDRMask(24, 32), + }) + assert.NoError(t, err, "should succeed") + + // Verify address was added to interface. + eth0, err := nw.InterfaceByName("eth0") + assert.NoError(t, err, "should succeed") + addrs, err := eth0.Addrs() + assert.NoError(t, err, "should succeed") + assert.Equal(t, 2, len(addrs), "should have 2 addresses") + + // Remove the address. + err = nw.RemoveAddress("eth0", newIP) + assert.NoError(t, err, "should succeed") + + // Verify address was removed. + addrs, err = eth0.Addrs() + assert.NoError(t, err, "should succeed") + assert.Equal(t, 1, len(addrs), "should have 1 address") + + // AddAddress with IP outside CIDR should fail. + err = nw.AddAddress("eth0", &net.IPNet{ + IP: net.ParseIP("192.168.1.1"), + Mask: net.CIDRMask(24, 32), + }) + assert.Error(t, err, "should fail for IP outside CIDR") + + // RemoveAddress with IPAddr type. + eth0.AddAddress(&net.IPAddr{IP: net.ParseIP("10.0.0.200")}) + removed := eth0.RemoveAddress(net.ParseIP("10.0.0.200")) + assert.True(t, removed, "should remove IPAddr") + + // RemoveAddress skips unsupported addr types. + eth0.AddAddress(&net.TCPAddr{IP: net.ParseIP("10.0.0.201"), Port: 1234}) + removed = eth0.RemoveAddress(net.ParseIP("10.0.0.201")) + assert.False(t, removed, "TCPAddr not supported") + + // RemoveAddress with non-existent IP. + removed = eth0.RemoveAddress(net.ParseIP("10.0.0.250")) + assert.False(t, removed, "should return false") + + assert.NoError(t, router.Stop(), "should succeed") + }) } diff --git a/vnet/router.go b/vnet/router.go index 0661ac3..62f5107 100644 --- a/vnet/router.go +++ b/vnet/router.go @@ -334,6 +334,21 @@ func (r *Router) addNIC(nic NIC) error { return nic.setRouter(r) } +// caller must hold the mutex. +func (r *Router) addIPToNIC(nic NIC, ip net.IP) error { + if !r.ipv4Net.Contains(ip) { + return fmt.Errorf("%w: %s", errStaticIPisBeyondSubnet, r.ipv4Net.String()) + } + r.nics[ip.String()] = nic + + return nil +} + +// caller must hold the mutex. +func (r *Router) removeIPFromNIC(ip net.IP) { + delete(r.nics, ip.String()) +} + // AddRouter adds a child Router. func (r *Router) AddRouter(router *Router) error { r.mutex.Lock()