aghnet: imp remaining functions

This commit is contained in:
f.setrakov 2025-09-19 13:18:31 +03:00
parent 361ec09c19
commit b48baa52c0
3 changed files with 114 additions and 77 deletions

View File

@ -90,8 +90,8 @@ func checkOtherDHCPv4(iface *net.Interface) (ok bool, err error) {
return discover4(iface, dstAddr, hostname)
}
// TODO(f.setrakov): continue refactoring, reduce cognitive
// complexity.
// discover4 sends a DHCPv4 discovery to the specified network interface and
// waits for response. iface and dstAddr must not be nil.
func discover4(iface *net.Interface, dstAddr *net.UDPAddr, hostname string) (ok bool, err error) {
var req *dhcpv4.DHCPv4
if req, err = dhcpv4.NewDiscovery(iface.HardwareAddr); err != nil {
@ -122,17 +122,9 @@ func discover4(iface *net.Interface, dstAddr *net.UDPAddr, hostname string) (ok
}
for {
if err = c.SetDeadline(time.Now().Add(defaultDiscoverTime)); err != nil {
return false, fmt.Errorf("setting deadline: %w", err)
}
var next bool
ok, next, err = tryConn4(req, c, iface)
ok, next, err = getDHCP4Response(req, c, iface)
if next {
if err != nil {
log.Debug("dhcpv4: trying a connection: %s", err)
}
continue
}
@ -144,9 +136,34 @@ func discover4(iface *net.Interface, dstAddr *net.UDPAddr, hostname string) (ok
}
}
// getDHCP4Response reads and validates DHCP response from [net.PacketConn].
// req, c and iface must not be nil.
func getDHCP4Response(req *dhcpv4.DHCPv4, c net.PacketConn, iface *net.Interface) (ok, next bool, err error) {
ok, next, err = tryConn4(req, c, iface)
if next {
if err != nil {
log.Debug("dhcpv4: trying a connection: %s", err)
}
return false, next, err
}
if err != nil {
return false, false, err
}
return ok, false, nil
}
// tryConn4 reads and validates DHCPv4 response packet if it matches
// the original request. req and c must not be nil.
//
// TODO(a.garipov): Refactor further. Inspect error handling, remove parameter
// next, address the TODO, merge with tryConn6, etc.
func tryConn4(req *dhcpv4.DHCPv4, c net.PacketConn, iface *net.Interface) (ok, next bool, err error) {
if err = c.SetDeadline(time.Now().Add(defaultDiscoverTime)); err != nil {
return false, false, fmt.Errorf("setting deadline: %w", err)
}
// TODO: replicate dhclient's behavior of retrying several times with
// progressively longer timeouts.
log.Tracef("dhcpv4: waiting %v for an answer", defaultDiscoverTime)
@ -223,8 +240,8 @@ func checkOtherDHCPv6(iface *net.Interface) (ok bool, err error) {
return discover6(iface, udpAddr, dstAddr)
}
// TODO(f.setrakov): continue refactoring, reduce cognitive
// complexity.
// discover6 sends a DHCPv6 discovery to the specified network interface and
// waits for response. iface, updAddr and dstAddr must not be nil.
func discover6(iface *net.Interface, udpAddr, dstAddr *net.UDPAddr) (ok bool, err error) {
req, err := dhcpv6.NewSolicit(iface.HardwareAddr)
if err != nil {
@ -245,12 +262,8 @@ func discover6(iface *net.Interface, udpAddr, dstAddr *net.UDPAddr) (ok bool, er
for {
var next bool
ok, next, err = tryConn6(req, c)
ok, next, err = getDHCP6Response(req, c)
if next {
if err != nil {
log.Debug("dhcpv6: trying a connection: %s", err)
}
continue
}
@ -262,6 +275,28 @@ func discover6(iface *net.Interface, udpAddr, dstAddr *net.UDPAddr) (ok bool, er
}
}
// getDHCP6Response reads and validates DHCP response from [net.PacketConn].
// req and c must not be nil.
func getDHCP6Response(req *dhcpv6.Message, c net.PacketConn) (ok, next bool, err error) {
ok, next, err = tryConn6(req, c)
if next {
if err != nil {
log.Debug("dhcpv6: trying a connection: %s", err)
}
return false, next, err
}
if err != nil {
return false, false, err
}
return ok, false, nil
}
// tryConn6 reads and validates DHCPv6 response packet if it matches
// the original request. req and c must not be nil.
//
// TODO(a.garipov): See the comment on tryConn4. Sigh…
func tryConn6(req *dhcpv6.Message, c net.PacketConn) (ok, next bool, err error) {
// TODO: replicate dhclient's behavior of retrying several times with

View File

@ -23,10 +23,7 @@ type NetIface interface {
Addrs() ([]net.Addr, error)
}
// IfaceIPAddrs returns the interface's IP addresses.
//
// TODO(f.setrakov): continue refactoring, reduce cognitive
// complexity.
// IfaceIPAddrs returns the interface's IP addresses. iface must noot be nil.
func IfaceIPAddrs(iface NetIface, ipv IPVersion) (ips []net.IP, err error) {
switch ipv {
case IPVersion4, IPVersion6:
@ -41,25 +38,7 @@ func IfaceIPAddrs(iface NetIface, ipv IPVersion) (ips []net.IP, err error) {
}
for _, a := range addrs {
var ip net.IP
switch a := a.(type) {
case *net.IPAddr:
ip = a.IP
case *net.IPNet:
ip = a.IP
default:
continue
}
// Assume that net.(*Interface).Addrs can only return valid IPv4 and
// IPv6 addresses. Thus, if it isn't an IPv4 address, it must be an
// IPv6 one.
ip4 := ip.To4()
if ipv == IPVersion4 {
if ip4 != nil {
ips = append(ips, ip4)
}
} else if ip4 == nil {
if ip := ipFromAddr(a, ipv); ip != nil {
ips = append(ips, ip)
}
}
@ -67,6 +46,30 @@ func IfaceIPAddrs(iface NetIface, ipv IPVersion) (ips []net.IP, err error) {
return ips, nil
}
// ipFromAddr converts addr to IP.
func ipFromAddr(addr net.Addr, ipv IPVersion) (ip net.IP) {
switch addr := addr.(type) {
case *net.IPAddr:
ip = addr.IP
case *net.IPNet:
ip = addr.IP
default:
return nil
}
// Assume that net.(*Interface).Addrs can only return valid IPv4 and
// IPv6 addresses. Thus, if it isn't an IPv4 address, it must be an
// IPv6 one.
ip4 := ip.To4()
if ipv == IPVersion4 {
return ip4
} else if ip4 == nil {
return ip
}
return nil
}
// IfaceDNSIPAddrs returns IP addresses of the interface suitable to send to
// clients as DNS addresses. If err is nil, addrs contains either no addresses
// or at least two.

View File

@ -136,7 +136,7 @@ func (iface NetInterface) MarshalJSON() ([]byte, error) {
}
// NetInterfaceFrom converts a [net.Interface] to [NetInterface], populating
// name, MAC address, flags, MTU, IP addresses, and subnets. iface must not be
// name, MAC address, flags, MTU, IP addresses, and subnets. iface must not be
// nil.
func NetInterfaceFrom(iface *net.Interface) (niface *NetInterface, err error) {
niface = &NetInterface{
@ -146,51 +146,50 @@ func NetInterfaceFrom(iface *net.Interface) (niface *NetInterface, err error) {
MTU: iface.MTU,
}
var addrs []net.Addr
addrs, err = iface.Addrs()
addrs, err := iface.Addrs()
if err != nil {
return nil, fmt.Errorf("failed to get addresses for interface %s: %w", iface.Name, err)
return nil, fmt.Errorf("getting addresses for interface %q: %w", iface.Name, err)
}
if err = populateAddrs(niface, iface.Name, addrs); err != nil {
return nil, err
for i, addr := range addrs {
if err = populateAddrs(addr, niface); err != nil {
return nil, fmt.Errorf("populating from addrs[%d]: %v", i, err)
}
}
return niface, nil
}
// populateAddrs fills [*NetInterface] IP addresses and subnets from [[]net.Addr].
func populateAddrs(niface *NetInterface, ifaceName string, addrs []net.Addr) (err error) {
for _, addr := range addrs {
var n *net.IPNet
n, err = ipNetFromAddr(addr)
if err != nil {
return err
}
ip, ok := netip.AddrFromSlice(n.IP)
if !ok {
return fmt.Errorf("bad address %s", n.IP)
}
ip = ip.Unmap()
// Skip link-local IPv4 addresses
if isLinkLocalV4(ip) {
continue
}
if ip.IsLinkLocalUnicast() {
ip = ip.WithZone(ifaceName)
}
ones, _ := n.Mask.Size()
p := netip.PrefixFrom(ip, ones)
niface.Addresses = append(niface.Addresses, ip)
niface.Subnets = append(niface.Subnets, p)
// populateAddrs fills [*NetInterface] IP addresses and subnets. niface
// must not be nil.
func populateAddrs(addr net.Addr, niface *NetInterface) (err error) {
n, err := ipNetFromAddr(addr)
if err != nil {
return err
}
ip, ok := netip.AddrFromSlice(n.IP)
if !ok {
return fmt.Errorf("bad address %s", n.IP)
}
ip = ip.Unmap()
// Skip link-local IPv4 addresses
if isLinkLocalV4(ip) {
return nil
}
if ip.IsLinkLocalUnicast() {
ip = ip.WithZone(niface.Name)
}
ones, _ := n.Mask.Size()
p := netip.PrefixFrom(ip, ones)
niface.Addresses = append(niface.Addresses, ip)
niface.Subnets = append(niface.Subnets, p)
return nil
}