Pull request: 3289 freebsd dhcp

Merge in DNS/adguard-home from 3289-freebsd-dhcp to master

Updates #3289.

Squashed commit of the following:

commit 1365d8f17293da611b860525d519a7bbd7851902
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Fri Jul 30 15:01:13 2021 +0300

    dhcpd: fix doc

commit 26724df27e92d457c39c8bf0fb78179a874e3fb2
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Fri Jul 30 14:52:58 2021 +0300

    all: imp code & docs

commit 9a9574a885d3d2129ef54fefb9a56857ce060cff
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Thu Jul 29 15:51:07 2021 +0300

    all: fix broadcasting, sup freebsd dhcp, fix http response
This commit is contained in:
Eugene Burkov 2021-07-30 15:27:24 +03:00
parent 63ee95dfbe
commit 6fa1167251
7 changed files with 178 additions and 11 deletions

View File

@ -15,6 +15,7 @@ and this project adheres to
### Added ### Added
- Static IP address detection on FreeBSD ([#3289]).
- Optimistic cache ([#2145]). - Optimistic cache ([#2145]).
- New possible value of `6h` for `querylog_interval` setting ([#2504]). - New possible value of `6h` for `querylog_interval` setting ([#2504]).
- Blocking access using client IDs ([#2624], [#3162]). - Blocking access using client IDs ([#2624], [#3162]).
@ -62,6 +63,7 @@ and this project adheres to
### Fixed ### Fixed
- Incomplete HTTP response for static IP address.
- DNSCrypt queries weren't appearing in query log ([#3372]). - DNSCrypt queries weren't appearing in query log ([#3372]).
- Wrong IP address for proxied DNS-over-HTTPS queries ([#2799]). - Wrong IP address for proxied DNS-over-HTTPS queries ([#2799]).
- Domain name letter case mismatches in DNS rewrites ([#3351]). - Domain name letter case mismatches in DNS rewrites ([#3351]).
@ -108,6 +110,7 @@ and this project adheres to
[#3217]: https://github.com/AdguardTeam/AdGuardHome/issues/3217 [#3217]: https://github.com/AdguardTeam/AdGuardHome/issues/3217
[#3256]: https://github.com/AdguardTeam/AdGuardHome/issues/3256 [#3256]: https://github.com/AdguardTeam/AdGuardHome/issues/3256
[#3257]: https://github.com/AdguardTeam/AdGuardHome/issues/3257 [#3257]: https://github.com/AdguardTeam/AdGuardHome/issues/3257
[#3289]: https://github.com/AdguardTeam/AdGuardHome/issues/3289
[#3335]: https://github.com/AdguardTeam/AdGuardHome/issues/3335 [#3335]: https://github.com/AdguardTeam/AdGuardHome/issues/3335
[#3343]: https://github.com/AdguardTeam/AdGuardHome/issues/3343 [#3343]: https://github.com/AdguardTeam/AdGuardHome/issues/3343
[#3351]: https://github.com/AdguardTeam/AdGuardHome/issues/3351 [#3351]: https://github.com/AdguardTeam/AdGuardHome/issues/3351

View File

@ -0,0 +1,78 @@
//go:build freebsd
// +build freebsd
package aghnet
import (
"bufio"
"fmt"
"io"
"net"
"os"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/golibs/errors"
)
func canBindPrivilegedPorts() (can bool, err error) {
return aghos.HaveAdminRights()
}
// maxCheckedFileSize is the maximum acceptable length of the /etc/rc.conf file.
const maxCheckedFileSize = 1024 * 1024
func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
const filename = "/etc/rc.conf"
var f *os.File
f, err = os.Open(filename)
if err != nil {
return false, err
}
defer func() { err = errors.WithDeferred(err, f.Close()) }()
var r io.Reader
r, err = aghio.LimitReader(f, maxCheckedFileSize)
if err != nil {
return false, err
}
return rcConfStaticConfig(r, ifaceName)
}
// rcConfStaticConfig checks if the interface is configured by /etc/rc.conf to
// have a static IP.
func rcConfStaticConfig(r io.Reader, ifaceName string) (has bool, err error) {
s := bufio.NewScanner(r)
for ifaceLinePref := fmt.Sprintf("ifconfig_%s", ifaceName); s.Scan(); {
line := strings.TrimSpace(s.Text())
if !strings.HasPrefix(line, ifaceLinePref) {
continue
}
eqIdx := len(ifaceLinePref)
if line[eqIdx] != '=' {
continue
}
fieldsStart, fieldsEnd := eqIdx+2, len(line)-1
if fieldsStart >= fieldsEnd {
continue
}
fields := strings.Fields(line[fieldsStart:fieldsEnd])
if len(fields) >= 2 &&
strings.ToLower(fields[0]) == "inet" &&
net.ParseIP(fields[1]) != nil {
return true, s.Err()
}
}
return false, s.Err()
}
func ifaceSetStaticIP(string) (err error) {
return aghos.Unsupported("setting static ip")
}

View File

@ -0,0 +1,60 @@
//go:build freebsd
// +build freebsd
package aghnet
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRcConfStaticConfig(t *testing.T) {
const ifaceName = `em0`
const nl = "\n"
testCases := []struct {
name string
rcconfData string
wantHas bool
}{{
name: "simple",
rcconfData: `ifconfig_em0="inet 127.0.0.253 netmask 0xffffffff"` + nl,
wantHas: true,
}, {
name: "case_insensitiveness",
rcconfData: `ifconfig_em0="InEt 127.0.0.253 NeTmAsK 0xffffffff"` + nl,
wantHas: true,
}, {
name: "comments_and_trash",
rcconfData: `# comment 1` + nl +
`` + nl +
`# comment 2` + nl +
`ifconfig_em0="inet 127.0.0.253 netmask 0xffffffff"` + nl,
wantHas: true,
}, {
name: "aliases",
rcconfData: `ifconfig_em0_alias="inet 127.0.0.1/24"` + nl +
`ifconfig_em0="inet 127.0.0.253 netmask 0xffffffff"` + nl,
wantHas: true,
}, {
name: "incorrect_config",
rcconfData: `ifconfig_em0="inet6 127.0.0.253 netmask 0xffffffff"` + nl +
`ifconfig_em0="inet 127.0.0.253 net-mask 0xffffffff"` + nl +
`ifconfig_em0="inet 256.256.256.256 netmask 0xffffffff"` + nl +
`ifconfig_em0=""` + nl,
wantHas: false,
}}
for _, tc := range testCases {
r := strings.NewReader(tc.rcconfData)
t.Run(tc.name, func(t *testing.T) {
has, err := rcConfStaticConfig(r, ifaceName)
require.NoError(t, err)
assert.Equal(t, tc.wantHas, has)
})
}
}

View File

@ -1,12 +1,9 @@
//go:build !(linux || darwin) //go:build !(linux || darwin || freebsd)
// +build !linux,!darwin // +build !linux,!darwin,!freebsd
package aghnet package aghnet
import ( import (
"fmt"
"runtime"
"github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/AdGuardHome/internal/aghos"
) )
@ -14,10 +11,10 @@ func canBindPrivilegedPorts() (can bool, err error) {
return aghos.HaveAdminRights() return aghos.HaveAdminRights()
} }
func ifaceHasStaticIP(string) (bool, error) { func ifaceHasStaticIP(string) (ok bool, err error) {
return false, fmt.Errorf("cannot check if IP is static: not supported on %s", runtime.GOOS) return false, aghos.Unsupported("checking static ip")
} }
func ifaceSetStaticIP(string) error { func ifaceSetStaticIP(string) (err error) {
return fmt.Errorf("cannot set static IP on %s", runtime.GOOS) return aghos.Unsupported("setting static ip")
} }

View File

@ -412,7 +412,9 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque
result := dhcpSearchResult{ result := dhcpSearchResult{
V4: dhcpSearchV4Result{ V4: dhcpSearchV4Result{
OtherServer: dhcpSearchOtherResult{}, OtherServer: dhcpSearchOtherResult{},
StaticIP: dhcpStaticIPStatus{}, StaticIP: dhcpStaticIPStatus{
Static: "yes",
},
}, },
V6: dhcpSearchV6Result{ V6: dhcpSearchV6Result{
OtherServer: dhcpSearchOtherResult{}, OtherServer: dhcpSearchOtherResult{},

View File

@ -38,6 +38,9 @@ type V4ServerConf struct {
GatewayIP net.IP `yaml:"gateway_ip" json:"gateway_ip"` GatewayIP net.IP `yaml:"gateway_ip" json:"gateway_ip"`
SubnetMask net.IP `yaml:"subnet_mask" json:"subnet_mask"` SubnetMask net.IP `yaml:"subnet_mask" json:"subnet_mask"`
// broadcastIP is the broadcasting address pre-calculated from the
// configured gateway IP and subnet mask.
broadcastIP net.IP
// The first & the last IP address for dynamic leases // The first & the last IP address for dynamic leases
// Bytes [0..2] of the last allowed IP address must match the first IP // Bytes [0..2] of the last allowed IP address must match the first IP

View File

@ -927,12 +927,30 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
resp.Options.Update(dhcpv4.OptMessageType(dhcpv4.MessageTypeNak)) resp.Options.Update(dhcpv4.OptMessageType(dhcpv4.MessageTypeNak))
} }
// peer is expected to be of type *net.UDPConn as the server4.NewServer
// initializes it.
udpPeer, ok := peer.(*net.UDPAddr)
if !ok {
log.Error("dhcpv4: peer is of unexpected type %T", peer)
return
}
// Despite the fact that server4.NewIPv4UDPConn explicitly sets socket
// options to allow broadcasting, it also binds the connection to a
// specific interface. On FreeBSD conn.WriteTo causes errors while
// writing to the addresses that belong to another interface. So, use
// the broadcast address specific for the binded interface in case
// server4.Server.Serve sets it to net.IPv4Bcast.
if udpPeer.IP.Equal(net.IPv4bcast) {
udpPeer.IP = s.conf.broadcastIP
}
log.Debug("dhcpv4: sending: %s", resp.Summary()) log.Debug("dhcpv4: sending: %s", resp.Summary())
_, err = conn.WriteTo(resp.ToBytes(), peer) _, err = conn.WriteTo(resp.ToBytes(), peer)
if err != nil { if err != nil {
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err) log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
return
} }
} }
@ -1043,6 +1061,12 @@ func v4Create(conf V4ServerConf) (srv DHCPServer, err error) {
Mask: subnetMask, Mask: subnetMask,
} }
bcastIP := aghnet.CloneIP(routerIP)
for i, b := range subnetMask {
bcastIP[i] |= ^b
}
s.conf.broadcastIP = bcastIP
s.conf.ipRange, err = newIPRange(conf.RangeStart, conf.RangeEnd) s.conf.ipRange, err = newIPRange(conf.RangeStart, conf.RangeEnd)
if err != nil { if err != nil {
return s, fmt.Errorf("dhcpv4: %w", err) return s, fmt.Errorf("dhcpv4: %w", err)