From 3cc5bf210d432ef1c75e25fba8f7bb20d8e213b9 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 9 Nov 2020 19:27:04 +0300 Subject: [PATCH] Pull request: * dhcpd: send secondary dns as well Merge in DNS/adguard-home from 1708-secondary-dns to master Updates #1708. Squashed commit of the following: commit 4529452e31131763f00c9c834cc95638f1a3d142 Author: Ainar Garipov Date: Mon Nov 9 18:12:57 2020 +0300 * dhcpd: send secondary dns as well --- internal/dhcpd/check_other_dhcp.go | 105 ++++++++++++++++++----------- internal/dhcpd/dhcp_http.go | 2 +- internal/dhcpd/helpers.go | 20 ------ internal/dhcpd/v4.go | 64 +++++++++++++++--- internal/dhcpd/v6.go | 46 +++++++++---- 5 files changed, 152 insertions(+), 85 deletions(-) diff --git a/internal/dhcpd/check_other_dhcp.go b/internal/dhcpd/check_other_dhcp.go index bf3ff1cc..0292528c 100644 --- a/internal/dhcpd/check_other_dhcp.go +++ b/internal/dhcpd/check_other_dhcp.go @@ -26,12 +26,16 @@ func CheckIfOtherDHCPServersPresentV4(ifaceName string) (bool, error) { return false, fmt.Errorf("couldn't find interface by name %s: %w", ifaceName, err) } - // get ipv4 address of an interface - ifaceIPNet := getIfaceIPv4(*iface) + ifaceIPNet, err := ifaceIPv4Addrs(iface) + if err != nil { + return false, fmt.Errorf("getting ipv4 addrs for iface %s: %w", ifaceName, err) + } if len(ifaceIPNet) == 0 { - return false, fmt.Errorf("couldn't find IPv4 address of interface %s %+v", ifaceName, iface) + return false, fmt.Errorf("interface %s has no ipv4 addresses", ifaceName) } + // TODO(a.garipov): Find out what this is about. Perhaps this + // information is outdated or at least incomplete. if runtime.GOOS == "darwin" { return false, fmt.Errorf("can't find DHCP server: not supported on macOS") } @@ -82,46 +86,66 @@ func CheckIfOtherDHCPServersPresentV4(ifaceName string) (bool, error) { } for { - // wait for answer - log.Tracef("Waiting %v for an answer", defaultDiscoverTime) - // TODO: replicate dhclient's behaviour of retrying several times with progressively bigger timeouts - b := make([]byte, 1500) - _ = c.SetReadDeadline(time.Now().Add(defaultDiscoverTime)) - n, _, err := c.ReadFrom(b) - if isTimeout(err) { - // timed out -- no DHCP servers - log.Debug("DHCPv4: didn't receive DHCP response") - return false, nil - } - if err != nil { - return false, fmt.Errorf("couldn't receive packet: %w", err) - } - - log.Tracef("Received packet (%v bytes)", n) - - response, err := dhcpv4.FromBytes(b[:n]) - if err != nil { - log.Debug("DHCPv4: dhcpv4.FromBytes: %s", err) + ok, next, err := tryConn(req, c, iface) + if next { continue } - - log.Debug("DHCPv4: received message from server: %s", response.Summary()) - - if !(response.OpCode == dhcpv4.OpcodeBootReply && - response.HWType == iana.HWTypeEthernet && - bytes.Equal(response.ClientHWAddr, iface.HardwareAddr) && - bytes.Equal(response.TransactionID[:], req.TransactionID[:]) && - response.Options.Has(dhcpv4.OptionDHCPMessageType)) { - log.Debug("DHCPv4: received message from server doesn't match our request") - continue + if ok { + return true, nil + } + if err != nil { + log.Debug("%s", err) } - - log.Tracef("The packet is from an active DHCP server") - // that's a DHCP server there - return true, nil } } +// TODO(a.garipov): Refactor further. Inspect error handling, remove the next +// parameter, address the TODO, etc. +func tryConn(req *dhcpv4.DHCPv4, c net.PacketConn, iface *net.Interface) (ok, next bool, err error) { + // TODO: replicate dhclient's behavior of retrying several times with + // progressively longer timeouts. + log.Tracef("waiting %v for an answer", defaultDiscoverTime) + + b := make([]byte, 1500) + _ = c.SetReadDeadline(time.Now().Add(defaultDiscoverTime)) + n, _, err := c.ReadFrom(b) + if err != nil { + if isTimeout(err) { + log.Debug("dhcpv4: didn't receive dhcp response") + + return false, false, nil + } + + return false, false, fmt.Errorf("receiving packet: %w", err) + } + + log.Tracef("received packet, %d bytes", n) + + response, err := dhcpv4.FromBytes(b[:n]) + if err != nil { + log.Debug("dhcpv4: encoding: %s", err) + + return false, true, err + } + + log.Debug("dhcpv4: received message from server: %s", response.Summary()) + + if !(response.OpCode == dhcpv4.OpcodeBootReply && + response.HWType == iana.HWTypeEthernet && + bytes.Equal(response.ClientHWAddr, iface.HardwareAddr) && + bytes.Equal(response.TransactionID[:], req.TransactionID[:]) && + response.Options.Has(dhcpv4.OptionDHCPMessageType)) { + + log.Debug("dhcpv4: received message from server doesn't match our request") + + return false, true, nil + } + + log.Tracef("the packet is from an active dhcp server") + + return true, false, nil +} + // CheckIfOtherDHCPServersPresentV6 sends a DHCP request to the specified network interface, // and waits for a response for a period defined by defaultDiscoverTime func CheckIfOtherDHCPServersPresentV6(ifaceName string) (bool, error) { @@ -130,9 +154,12 @@ func CheckIfOtherDHCPServersPresentV6(ifaceName string) (bool, error) { return false, fmt.Errorf("dhcpv6: net.InterfaceByName: %s: %w", ifaceName, err) } - ifaceIPNet := getIfaceIPv6(*iface) + ifaceIPNet, err := ifaceIPv6Addrs(iface) + if err != nil { + return false, fmt.Errorf("getting ipv6 addrs for iface %s: %w", ifaceName, err) + } if len(ifaceIPNet) == 0 { - return false, fmt.Errorf("dhcpv6: couldn't find IPv6 address of interface %s %+v", ifaceName, iface) + return false, fmt.Errorf("interface %s has no ipv6 addresses", ifaceName) } srcIP := ifaceIPNet[0] diff --git a/internal/dhcpd/dhcp_http.go b/internal/dhcpd/dhcp_http.go index fcb8b715..c91d5d64 100644 --- a/internal/dhcpd/dhcp_http.go +++ b/internal/dhcpd/dhcp_http.go @@ -335,7 +335,7 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque foundVal := "no" if found4 { foundVal = "yes" - } else if err != nil { + } else if err4 != nil { foundVal = "error" othSrv["error"] = err4.Error() } diff --git a/internal/dhcpd/helpers.go b/internal/dhcpd/helpers.go index cb7cf013..aafda988 100644 --- a/internal/dhcpd/helpers.go +++ b/internal/dhcpd/helpers.go @@ -14,26 +14,6 @@ func isTimeout(err error) bool { return operr.Timeout() } -// Get IPv4 address list -func getIfaceIPv4(iface net.Interface) []net.IP { - addrs, err := iface.Addrs() - if err != nil { - return nil - } - - var res []net.IP - for _, a := range addrs { - ipnet, ok := a.(*net.IPNet) - if !ok { - continue - } - if ipnet.IP.To4() != nil { - res = append(res, ipnet.IP.To4()) - } - } - return res -} - func parseIPv4(text string) (net.IP, error) { result := net.ParseIP(text) if result == nil { diff --git a/internal/dhcpd/v4.go b/internal/dhcpd/v4.go index e0acdad0..9ad032c1 100644 --- a/internal/dhcpd/v4.go +++ b/internal/dhcpd/v4.go @@ -36,7 +36,7 @@ func (s *v4Server) WriteDiskConfig6(c *V6ServerConf) { } // Return TRUE if IP address is within range [start..stop] -func ip4InRange(start net.IP, stop net.IP, ip net.IP) bool { +func ip4InRange(start, stop, ip net.IP) bool { if len(start) != 4 || len(stop) != 4 { return false } @@ -335,7 +335,7 @@ func (s *v4Server) commitLease(l *Lease) { } // Process Discover request and return lease -func (s *v4Server) processDiscover(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) *Lease { +func (s *v4Server) processDiscover(req, resp *dhcpv4.DHCPv4) *Lease { mac := req.ClientHWAddr s.leasesLock.Lock() @@ -409,7 +409,7 @@ func (o *optFQDN) ToBytes() []byte { // Process Request request and return lease // Return false if we don't need to reply -func (s *v4Server) processRequest(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) (*Lease, bool) { +func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (*Lease, bool) { var lease *Lease mac := req.ClientHWAddr hostname := req.Options.Get(dhcpv4.OptionHostName) @@ -472,7 +472,7 @@ func (s *v4Server) processRequest(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) (*Lea // Return 1: OK // Return 0: error; reply with Nak // Return -1: error; don't reply -func (s *v4Server) process(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) int { +func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int { var lease *Lease resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0])) @@ -554,24 +554,65 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4 } } -// Start - start server +// ifaceIPv4Addrs returns the interface's IPv4 addresses. +func ifaceIPv4Addrs(iface *net.Interface) (ips []net.IP, err error) { + addrs, err := iface.Addrs() + if err != nil { + return nil, err + } + + for _, a := range addrs { + ipnet, ok := a.(*net.IPNet) + if !ok { + continue + } + + if ip := ipnet.IP.To4(); ip != nil { + ips = append(ips, ip) + } + } + + return ips, nil +} + +// Start starts the IPv4 DHCP server. func (s *v4Server) Start() error { if !s.conf.Enabled { return nil } - iface, err := net.InterfaceByName(s.conf.InterfaceName) + ifaceName := s.conf.InterfaceName + iface, err := net.InterfaceByName(ifaceName) if err != nil { - return fmt.Errorf("dhcpv4: Couldn't find interface by name %s: %w", s.conf.InterfaceName, err) + return fmt.Errorf("dhcpv4: finding interface %s by name: %w", ifaceName, err) } log.Debug("dhcpv4: starting...") - s.conf.dnsIPAddrs = getIfaceIPv4(*iface) - if len(s.conf.dnsIPAddrs) == 0 { - log.Debug("dhcpv4: no IPv6 address for interface %s", iface.Name) - return nil + + dnsIPAddrs, err := ifaceIPv4Addrs(iface) + if err != nil { + return fmt.Errorf("dhcpv4: getting ipv4 addrs for iface %s: %w", ifaceName, err) } + switch len(dnsIPAddrs) { + case 0: + log.Debug("dhcpv4: no ipv4 address for interface %s", iface.Name) + + return nil + case 1: + // Some Android devices use 8.8.8.8 if there is no secondary DNS + // server. Fix that by setting the secondary DNS address to our + // address as well. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/1708. + log.Debug("dhcpv4: setting secondary dns ip to iself for interface %s", iface.Name) + dnsIPAddrs = append(dnsIPAddrs, dnsIPAddrs[0]) + default: + // Go on. + } + + s.conf.dnsIPAddrs = dnsIPAddrs + laddr := &net.UDPAddr{ IP: net.ParseIP("0.0.0.0"), Port: dhcpv4.ServerPort, @@ -587,6 +628,7 @@ func (s *v4Server) Start() error { err = s.srv.Serve() log.Debug("dhcpv4: srv.Serve: %s", err) }() + return nil } diff --git a/internal/dhcpd/v6.go b/internal/dhcpd/v6.go index 22505f7a..b24be499 100644 --- a/internal/dhcpd/v6.go +++ b/internal/dhcpd/v6.go @@ -537,24 +537,25 @@ func (s *v6Server) packetHandler(conn net.PacketConn, peer net.Addr, req dhcpv6. } } -// Get IPv6 address list -func getIfaceIPv6(iface net.Interface) []net.IP { +// ifaceIPv6Addrs returns the interface's IPv6 addresses. +func ifaceIPv6Addrs(iface *net.Interface) (ips []net.IP, err error) { addrs, err := iface.Addrs() if err != nil { - return nil + return nil, err } - var res []net.IP for _, a := range addrs { ipnet, ok := a.(*net.IPNet) if !ok { continue } - if ipnet.IP.To4() == nil { - res = append(res, ipnet.IP) + + if ip := ipnet.IP.To16(); ip != nil { + ips = append(ips, ip) } } - return res + + return ips, nil } // initialize RA module @@ -578,23 +579,40 @@ func (s *v6Server) initRA(iface *net.Interface) error { return s.ra.Init() } -// Start - start server +// Start starts the IPv6 DHCP server. func (s *v6Server) Start() error { if !s.conf.Enabled { return nil } - iface, err := net.InterfaceByName(s.conf.InterfaceName) + ifaceName := s.conf.InterfaceName + iface, err := net.InterfaceByName(ifaceName) if err != nil { - return fmt.Errorf("couldn't find interface by name %s: %w", s.conf.InterfaceName, err) + return fmt.Errorf("dhcpv6: finding interface %s by name: %w", ifaceName, err) } - s.conf.dnsIPAddrs = getIfaceIPv6(*iface) - if len(s.conf.dnsIPAddrs) == 0 { - log.Debug("DHCPv6: no IPv6 address for interface %s", iface.Name) - return nil + log.Debug("dhcpv4: starting...") + + dnsIPAddrs, err := ifaceIPv6Addrs(iface) + if err != nil { + return fmt.Errorf("dhcpv6: getting ipv6 addrs for iface %s: %w", ifaceName, err) } + switch len(dnsIPAddrs) { + case 0: + log.Debug("dhcpv6: no ipv6 address for interface %s", iface.Name) + + return nil + case 1: + // See the comment in (*v4Server).Start. + log.Debug("dhcpv6: setting secondary dns ip to iself for interface %s", iface.Name) + dnsIPAddrs = append(dnsIPAddrs, dnsIPAddrs[0]) + default: + // Go on. + } + + s.conf.dnsIPAddrs = dnsIPAddrs + err = s.initRA(iface) if err != nil { return err