Skip to content

Commit

Permalink
Pull request: dhcpd: wait for interfaces' ip addresses to appear
Browse files Browse the repository at this point in the history
Merge in DNS/adguard-home from 2304-dncp-backoff to master

Updates #2304.

Squashed commit of the following:

commit c9bff8b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Nov 27 14:08:03 2020 +0300

    dhcpd: try for 5s instead of 10s

commit 983cf47
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Nov 25 19:58:41 2020 +0300

    dhcpd: wait for interfaces' ip addresses to appear
  • Loading branch information
ainar-g committed Nov 27, 2020
1 parent f9e4e7b commit a6e18c4
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 171 deletions.
65 changes: 38 additions & 27 deletions HACKING.md
@@ -1,8 +1,9 @@
# *AdGuardHome* Developer Guidelines

As of **2020-11-20**, this document is still a work-in-progress. Some of the
rules aren't enforced, and others might change. Still, this is a good place to
find out about how we **want** our code to look like.
As of **2020-11-27**, this document is a work-in-progress, but should still be
followed. Some of the rules aren't enforced as thoroughly or remain broken in
old code, but this is still the place to find out about what we **want** our
code to look like.

The rules are mostly sorted in the alphabetical order.

Expand Down Expand Up @@ -31,27 +32,17 @@ The rules are mostly sorted in the alphabetical order.

## *Go*

* <https://github.com/golang/go/wiki/CodeReviewComments>.

* <https://github.com/golang/go/wiki/TestComments>.
### Code And Naming

* <https://go-proverbs.github.io/>

* Add an empty line before `break`, `continue`, and `return`, unless it's the
only statement in that block.
* Avoid `goto`.

* Avoid `init` and use explicit initialization functions instead.

* Avoid `new`, especially with structs.

* Document everything, including unexported top-level identifiers, to build
a habit of writing documentation.

* Constructors should validate their arguments and return meaningful errors.
As a corollary, avoid lazy initialization.

* Don't put variable names into any kind of quotes.

* Don't use naked `return`s.

* Don't use underscores in file and package names, unless they're build tags
Expand All @@ -76,24 +67,33 @@ The rules are mostly sorted in the alphabetical order.

* Name the deferred errors (e.g. when closing something) `cerr`.

* No `goto`.

* No shadowing, since it can often lead to subtle bugs, especially with
errors.

* Prefer constants to variables where possible. Reduce global variables. Use
[constant errors] instead of `errors.New`.

* Put comments above the documented entity, **not** to the side, to improve
readability.
* Use linters.

* Use `gofumpt --extra -s`.
* Use named returns to improve readability of function signatures.

**TODO(a.garipov):** Add to the linters.
* Write logs and error messages in lowercase only to make it easier to `grep`
logs and error messages without using the `-i` flag.

* Use linters.
[constant errors]: https://dave.cheney.net/2016/04/07/constant-errors
[Linus said]: https://www.kernel.org/doc/html/v4.17/process/coding-style.html#indentation

* Use named returns to improve readability of function signatures.
### Commenting

* See also the *Text, Including Comments* section below.

* Document everything, including unexported top-level identifiers, to build
a habit of writing documentation.

* Don't put identifiers into any kind of quotes.

* Put comments above the documented entity, **not** to the side, to improve
readability.

* When a method implements an interface, start the doc comment with the
standard template:
Expand All @@ -105,8 +105,14 @@ The rules are mostly sorted in the alphabetical order.
}
```

* Write logs and error messages in lowercase only to make it easier to `grep`
logs and error messages without using the `-i` flag.
### Formatting

* Add an empty line before `break`, `continue`, `fallthrough`, and `return`,
unless it's the only statement in that block.

* Use `gofumpt --extra -s`.

**TODO(a.garipov):** Add to the linters.

* Write slices of struct like this:

Expand All @@ -123,8 +129,13 @@ The rules are mostly sorted in the alphabetical order.
}}
```

[constant errors]: https://dave.cheney.net/2016/04/07/constant-errors
[Linus said]: https://www.kernel.org/doc/html/v4.17/process/coding-style.html#indentation
### Recommended Reading

* <https://github.com/golang/go/wiki/CodeReviewComments>.

* <https://github.com/golang/go/wiki/TestComments>.

* <https://go-proverbs.github.io/>

## *Markdown*

Expand Down
4 changes: 2 additions & 2 deletions internal/dhcpd/check_other_dhcp.go
Expand Up @@ -26,7 +26,7 @@ func CheckIfOtherDHCPServersPresentV4(ifaceName string) (bool, error) {
return false, fmt.Errorf("couldn't find interface by name %s: %w", ifaceName, err)
}

ifaceIPNet, err := ifaceIPv4Addrs(iface)
ifaceIPNet, err := ifaceIPAddrs(iface, ipVersion4)
if err != nil {
return false, fmt.Errorf("getting ipv4 addrs for iface %s: %w", ifaceName, err)
}
Expand Down Expand Up @@ -161,7 +161,7 @@ func CheckIfOtherDHCPServersPresentV6(ifaceName string) (bool, error) {
return false, fmt.Errorf("dhcpv6: net.InterfaceByName: %s: %w", ifaceName, err)
}

ifaceIPNet, err := ifaceIPv6Addrs(iface)
ifaceIPNet, err := ifaceIPAddrs(iface, ipVersion6)
if err != nil {
return false, fmt.Errorf("getting ipv6 addrs for iface %s: %w", ifaceName, err)
}
Expand Down
46 changes: 5 additions & 41 deletions internal/dhcpd/v4.go
Expand Up @@ -16,7 +16,9 @@ import (
"github.com/insomniacslk/dhcp/dhcpv4/server4"
)

// v4Server - DHCPv4 server
// v4Server is a DHCPv4 server.
//
// TODO(a.garipov): Think about unifying this and v6Server.
type v4Server struct {
srv *server4.Server
leasesLock sync.Mutex
Expand Down Expand Up @@ -560,27 +562,6 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
}
}

// 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 {
Expand All @@ -595,26 +576,9 @@ func (s *v4Server) Start() error {

log.Debug("dhcpv4: starting...")

dnsIPAddrs, err := ifaceIPv4Addrs(iface)
dnsIPAddrs, err := ifaceDNSIPAddrs(iface, ipVersion4, defaultMaxAttempts, defaultBackoff)
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.
return fmt.Errorf("dhcpv4: interface %s: %w", ifaceName, err)
}

s.conf.dnsIPAddrs = dnsIPAddrs
Expand Down
123 changes: 123 additions & 0 deletions internal/dhcpd/v46.go
@@ -0,0 +1,123 @@
package dhcpd

import (
"fmt"
"net"
"time"

"github.com/AdguardTeam/golibs/log"
)

// ipVersion is a documentational alias for int. Use it when the integer means
// IP version.
type ipVersion = int

// IP version constants.
const (
ipVersion4 ipVersion = 4
ipVersion6 ipVersion = 6
)

// netIface is the interface for network interface methods.
type netIface interface {
Addrs() ([]net.Addr, error)
}

// ifaceIPAddrs returns the interface's IP addresses.
func ifaceIPAddrs(iface netIface, ipv ipVersion) (ips []net.IP, err error) {
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}

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.
switch ipv {
case ipVersion4:
if ip4 := ip.To4(); ip4 != nil {
ips = append(ips, ip4)
}
case ipVersion6:
if ip6 := ip.To4(); ip6 == nil {
ips = append(ips, ip)
}
default:
return nil, fmt.Errorf("invalid ip version %d", ipv)
}
}

return ips, nil
}

// Currently used defaults for ifaceDNSAddrs.
const (
defaultMaxAttempts int = 10

defaultBackoff time.Duration = 500 * time.Millisecond
)

// 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.
//
// It makes up to maxAttempts attempts to get the addresses if there are none,
// each time using the provided backoff. Sometimes an interface needs a few
// seconds to really ititialize.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/2304.
func ifaceDNSIPAddrs(
iface netIface,
ipv ipVersion,
maxAttempts int,
backoff time.Duration,
) (addrs []net.IP, err error) {
var n int
waitForIP:
for n = 1; n <= maxAttempts; n++ {
addrs, err = ifaceIPAddrs(iface, ipv)
if err != nil {
return nil, fmt.Errorf("getting ip addrs: %w", err)
}

switch len(addrs) {
case 0:
log.Debug("dhcpv%d: attempt %d: no ip addresses", ipv, n)

time.Sleep(backoff)
case 1:
// Some Android devices use 8.8.8.8 if there is not
// a secondary DNS server. Fix that by setting the
// secondary DNS address to the same address.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/1708.
log.Debug("dhcpv%d: setting secondary dns ip to itself", ipv)
addrs = append(addrs, addrs[0])

fallthrough
default:
break waitForIP
}
}

if len(addrs) == 0 {
// Don't return errors in case the users want to try and enable
// the DHCP server later.
log.Error("dhcpv%d: no ip address for interface after %d attempts and %s", ipv, n, time.Duration(n)*backoff)
} else {
log.Debug("dhcpv%d: got addresses %s after %d attempts", ipv, addrs, n)
}

return addrs, nil
}

0 comments on commit a6e18c4

Please sign in to comment.