dhcpsvc: generalize

This commit is contained in:
Eugene Burkov 2024-01-30 18:51:49 +03:00
parent e4c2cae73a
commit 310ed67b9b
3 changed files with 44 additions and 66 deletions

View File

@ -36,10 +36,10 @@ type DHCPServer struct {
leaseByName map[string]*Lease
// interfaces4 is the set of IPv4 interfaces sorted by interface name.
interfaces4 []*netInterfaceV4
interfaces4 netInterfacesV4
// interfaces6 is the set of IPv6 interfaces sorted by interface name.
interfaces6 []*netInterfaceV6
interfaces6 netInterfacesV6
// icmpTimeout is the timeout for checking another DHCP server's presence.
icmpTimeout time.Duration
@ -55,8 +55,9 @@ func New(conf *Config) (srv *DHCPServer, err error) {
return nil, nil
}
ifaces4 := make([]*netInterfaceV4, 0, len(conf.Interfaces))
ifaces6 := make([]*netInterfaceV6, 0, len(conf.Interfaces))
// TODO(e.burkov): Add validations scoped to the network interfaces set.
ifaces4 := make(netInterfacesV4, 0, len(conf.Interfaces))
ifaces6 := make(netInterfacesV6, 0, len(conf.Interfaces))
ifaceNames := maps.Keys(conf.Interfaces)
slices.Sort(ifaceNames)
@ -181,9 +182,18 @@ func (srv *DHCPServer) Reset() (err error) {
// AddLease implements the [Interface] interface for *DHCPServer.
func (srv *DHCPServer) AddLease(l *Lease) (err error) {
iface, err := srv.netInterfaceForLease(l)
if err != nil {
return err
var ok bool
var iface *netInterface
addr := l.IP
if addr.Is4() {
iface, ok = srv.interfaces4.find(addr)
} else {
iface, ok = srv.interfaces6.find(addr)
}
if !ok {
return fmt.Errorf("no interface for IP address %s", addr)
}
srv.leasesMu.Lock()
@ -199,45 +209,3 @@ func (srv *DHCPServer) AddLease(l *Lease) (err error) {
return nil
}
// netInterfaceForLease returns the iface responsible for l, if any. l must
// have a valid IP address.
func (srv *DHCPServer) netInterfaceForLease(l *Lease) (iface *netInterface, err error) {
if addr := l.IP; addr.Is4() {
var iface4 *netInterfaceV4
iface4, err = netInterfaceForAddr(srv.interfaces4, addr)
if err != nil {
return nil, err
}
iface = &iface4.netInterface
} else {
var iface6 *netInterfaceV6
iface6, err = netInterfaceForAddr(srv.interfaces6, addr)
if err != nil {
return nil, err
}
iface = &iface6.netInterface
}
return iface, nil
}
// netInterface is a network interface.
type netInterfaceAny interface {
contains(addr netip.Addr) (ok bool)
}
// netInterfaceForAddr returns the first network interface from ifaces that
// contains addr. It returns an error if there is no such interface in ifaces.
func netInterfaceForAddr[I netInterfaceAny](ifaces []I, addr netip.Addr) (iface I, err error) {
i := slices.IndexFunc(ifaces, func(iface I) (ok bool) {
return iface.contains(addr)
})
if i < 0 {
return iface, fmt.Errorf("no interface for IP address %s", addr)
}
return ifaces[i], nil
}

View File

@ -320,10 +320,18 @@ func newNetInterfaceV4(name string, conf *IPv4Config) (i *netInterfaceV4, err er
return i, nil
}
// type check
var _ netInterfaceAny = (*netInterfaceV4)(nil)
// netInterfacesV4 is a slice of network interfaces of IPv4 address family.
type netInterfacesV4 []*netInterfaceV4
// contains returns true if ip is within the address space allocated for i.
//
// TODO(e.burkov): Make this a method of [netInterface].
func (i *netInterfaceV4) contains(ip netip.Addr) (ok bool) { return i.subnet.Contains(ip) }
// find returns the first network interface within ifaces containing ip. It
// returns false if there is no such interface.
func (ifaces netInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, ok bool) {
i := slices.IndexFunc(ifaces, func(iface *netInterfaceV4) bool {
return iface.subnet.Contains(ip)
})
if i < 0 {
return nil, false
}
return &ifaces[i].netInterface, true
}

View File

@ -134,16 +134,18 @@ func newNetInterfaceV6(name string, conf *IPv6Config) (i *netInterfaceV6) {
return i
}
// type check
var _ netInterfaceAny = (*netInterfaceV4)(nil)
// netInterfacesV4 is a slice of network interfaces of IPv4 address family.
type netInterfacesV6 []*netInterfaceV6
// contains returns true if ip is within the address space allocated for i.
//
// TODO(e.burkov): DHCPv6 inherits the weird behavior of legacy implementation
// where the allocated range constrained by the first address and the first
// address with last byte set to 0xff. Proper prefixes should be used instead.
//
// TODO(e.burkov): Make this a method of [netInterface].
func (i *netInterfaceV6) contains(ip netip.Addr) (ok bool) {
return !i.rangeStart.Less(ip) && netip.PrefixFrom(i.rangeStart, 120).Contains(ip)
// find returns the first network interface within ifaces containing ip. It
// returns false if there is no such interface.
func (ifaces netInterfacesV6) find(ip netip.Addr) (iface6 *netInterface, ok bool) {
i := slices.IndexFunc(ifaces, func(iface *netInterfaceV6) bool {
return !iface.rangeStart.Less(ip) && netip.PrefixFrom(iface.rangeStart, 120).Contains(ip)
})
if i < 0 {
return nil, false
}
return &ifaces[i].netInterface, true
}