all: sync with master; upd chlog
This commit is contained in:
@@ -16,17 +16,17 @@ To set up a test environment for DHCP server you will need:
|
||||
|
||||
### Configure Virtual Box
|
||||
|
||||
1. Install Virtual Box and run the following command to create a Host-Only
|
||||
1. Install Virtual Box and run the following command to create a Host-Only
|
||||
network:
|
||||
|
||||
```sh
|
||||
$ VBoxManage hostonlyif create
|
||||
```
|
||||
|
||||
You can check its status by `ip a` command.
|
||||
|
||||
You can also set up Host-Only network using Virtual Box menu:
|
||||
|
||||
You can check its status by `ip a` command.
|
||||
|
||||
You can also set up Host-Only network using Virtual Box menu:
|
||||
|
||||
```
|
||||
File -> Host Network Manager...
|
||||
```
|
||||
|
||||
@@ -23,9 +23,12 @@ type ServerConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
InterfaceName string `yaml:"interface_name"`
|
||||
|
||||
// LocalDomainName is the domain name used for DHCP hosts. For example,
|
||||
// a DHCP client with the hostname "myhost" can be addressed as "myhost.lan"
|
||||
// LocalDomainName is the domain name used for DHCP hosts. For example, a
|
||||
// DHCP client with the hostname "myhost" can be addressed as "myhost.lan"
|
||||
// when LocalDomainName is "lan".
|
||||
//
|
||||
// TODO(e.burkov): Probably, remove this field. See the TODO on
|
||||
// [Interface.Enabled].
|
||||
LocalDomainName string `yaml:"local_domain_name"`
|
||||
|
||||
Conf4 V4ServerConf `yaml:"dhcpv4"`
|
||||
@@ -58,6 +61,14 @@ type DHCPServer interface {
|
||||
// there is one.
|
||||
FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr)
|
||||
|
||||
// HostByIP returns a hostname by the IP address of its lease, if there is
|
||||
// one.
|
||||
HostByIP(ip netip.Addr) (host string)
|
||||
|
||||
// IPByHost returns an IP address by the hostname of its lease, if there is
|
||||
// one.
|
||||
IPByHost(host string) (ip netip.Addr)
|
||||
|
||||
// WriteDiskConfig4 - copy disk configuration
|
||||
WriteDiskConfig4(c *V4ServerConf)
|
||||
// WriteDiskConfig6 - copy disk configuration
|
||||
|
||||
@@ -81,21 +81,6 @@ func (s *v4Server) newDHCPConn(iface *net.Interface) (c net.PacketConn, err erro
|
||||
}, nil
|
||||
}
|
||||
|
||||
// wrapErrs is a helper to wrap the errors from two independent underlying
|
||||
// connections.
|
||||
func (*dhcpConn) wrapErrs(action string, udpConnErr, rawConnErr error) (err error) {
|
||||
switch {
|
||||
case udpConnErr != nil && rawConnErr != nil:
|
||||
return errors.List(fmt.Sprintf("%s both connections", action), udpConnErr, rawConnErr)
|
||||
case udpConnErr != nil:
|
||||
return fmt.Errorf("%s udp connection: %w", action, udpConnErr)
|
||||
case rawConnErr != nil:
|
||||
return fmt.Errorf("%s raw connection: %w", action, rawConnErr)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTo implements net.PacketConn for *dhcpConn. It selects the underlying
|
||||
// connection to write to based on the type of addr.
|
||||
func (c *dhcpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
@@ -117,7 +102,7 @@ func (c *dhcpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
Port: dhcpv4.ClientPort,
|
||||
})
|
||||
|
||||
return n, c.wrapErrs("writing to", uerr, rerr)
|
||||
return n, wrapErrs("writing to", uerr, rerr)
|
||||
case *net.UDPAddr:
|
||||
if addr.IP.Equal(net.IPv4bcast) {
|
||||
// Broadcast the message for the client which supports
|
||||
@@ -157,7 +142,7 @@ func (c *dhcpConn) Close() (err error) {
|
||||
rerr = nil
|
||||
}
|
||||
|
||||
return c.wrapErrs("closing", c.udpConn.Close(), rerr)
|
||||
return wrapErrs("closing", c.udpConn.Close(), rerr)
|
||||
}
|
||||
|
||||
// LocalAddr implements net.PacketConn for *dhcpConn.
|
||||
@@ -167,12 +152,12 @@ func (c *dhcpConn) LocalAddr() (a net.Addr) {
|
||||
|
||||
// SetDeadline implements net.PacketConn for *dhcpConn.
|
||||
func (c *dhcpConn) SetDeadline(t time.Time) (err error) {
|
||||
return c.wrapErrs("setting deadline on", c.udpConn.SetDeadline(t), c.rawConn.SetDeadline(t))
|
||||
return wrapErrs("setting deadline on", c.udpConn.SetDeadline(t), c.rawConn.SetDeadline(t))
|
||||
}
|
||||
|
||||
// SetReadDeadline implements net.PacketConn for *dhcpConn.
|
||||
func (c *dhcpConn) SetReadDeadline(t time.Time) error {
|
||||
return c.wrapErrs(
|
||||
return wrapErrs(
|
||||
"setting reading deadline on",
|
||||
c.udpConn.SetReadDeadline(t),
|
||||
c.rawConn.SetReadDeadline(t),
|
||||
@@ -181,7 +166,7 @@ func (c *dhcpConn) SetReadDeadline(t time.Time) error {
|
||||
|
||||
// SetWriteDeadline implements net.PacketConn for *dhcpConn.
|
||||
func (c *dhcpConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.wrapErrs(
|
||||
return wrapErrs(
|
||||
"setting writing deadline on",
|
||||
c.udpConn.SetWriteDeadline(t),
|
||||
c.rawConn.SetWriteDeadline(t),
|
||||
|
||||
@@ -79,21 +79,6 @@ func (s *v4Server) newDHCPConn(iface *net.Interface) (c net.PacketConn, err erro
|
||||
}, nil
|
||||
}
|
||||
|
||||
// wrapErrs is a helper to wrap the errors from two independent underlying
|
||||
// connections.
|
||||
func (*dhcpConn) wrapErrs(action string, udpConnErr, rawConnErr error) (err error) {
|
||||
switch {
|
||||
case udpConnErr != nil && rawConnErr != nil:
|
||||
return errors.List(fmt.Sprintf("%s both connections", action), udpConnErr, rawConnErr)
|
||||
case udpConnErr != nil:
|
||||
return fmt.Errorf("%s udp connection: %w", action, udpConnErr)
|
||||
case rawConnErr != nil:
|
||||
return fmt.Errorf("%s raw connection: %w", action, rawConnErr)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTo implements net.PacketConn for *dhcpConn. It selects the underlying
|
||||
// connection to write to based on the type of addr.
|
||||
func (c *dhcpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
@@ -115,7 +100,7 @@ func (c *dhcpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
Port: dhcpv4.ClientPort,
|
||||
})
|
||||
|
||||
return n, c.wrapErrs("writing to", uerr, rerr)
|
||||
return n, wrapErrs("writing to", uerr, rerr)
|
||||
case *net.UDPAddr:
|
||||
if addr.IP.Equal(net.IPv4bcast) {
|
||||
// Broadcast the message for the client which supports
|
||||
@@ -155,7 +140,7 @@ func (c *dhcpConn) Close() (err error) {
|
||||
rerr = nil
|
||||
}
|
||||
|
||||
return c.wrapErrs("closing", c.udpConn.Close(), rerr)
|
||||
return wrapErrs("closing", c.udpConn.Close(), rerr)
|
||||
}
|
||||
|
||||
// LocalAddr implements net.PacketConn for *dhcpConn.
|
||||
@@ -165,12 +150,12 @@ func (c *dhcpConn) LocalAddr() (a net.Addr) {
|
||||
|
||||
// SetDeadline implements net.PacketConn for *dhcpConn.
|
||||
func (c *dhcpConn) SetDeadline(t time.Time) (err error) {
|
||||
return c.wrapErrs("setting deadline on", c.udpConn.SetDeadline(t), c.rawConn.SetDeadline(t))
|
||||
return wrapErrs("setting deadline on", c.udpConn.SetDeadline(t), c.rawConn.SetDeadline(t))
|
||||
}
|
||||
|
||||
// SetReadDeadline implements net.PacketConn for *dhcpConn.
|
||||
func (c *dhcpConn) SetReadDeadline(t time.Time) error {
|
||||
return c.wrapErrs(
|
||||
return wrapErrs(
|
||||
"setting reading deadline on",
|
||||
c.udpConn.SetReadDeadline(t),
|
||||
c.rawConn.SetReadDeadline(t),
|
||||
@@ -179,7 +164,7 @@ func (c *dhcpConn) SetReadDeadline(t time.Time) error {
|
||||
|
||||
// SetWriteDeadline implements net.PacketConn for *dhcpConn.
|
||||
func (c *dhcpConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.wrapErrs(
|
||||
return wrapErrs(
|
||||
"setting writing deadline on",
|
||||
c.udpConn.SetWriteDeadline(t),
|
||||
c.rawConn.SetWriteDeadline(t),
|
||||
|
||||
24
internal/dhcpd/conn_unix.go
Normal file
24
internal/dhcpd/conn_unix.go
Normal file
@@ -0,0 +1,24 @@
|
||||
//go:build darwin || freebsd || linux || openbsd
|
||||
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
)
|
||||
|
||||
// wrapErrs is a helper to wrap the errors from two independent underlying
|
||||
// connections.
|
||||
func wrapErrs(action string, udpConnErr, rawConnErr error) (err error) {
|
||||
switch {
|
||||
case udpConnErr != nil && rawConnErr != nil:
|
||||
return fmt.Errorf("%s both connections: %s", action, errors.Join(udpConnErr, rawConnErr))
|
||||
case udpConnErr != nil:
|
||||
return fmt.Errorf("%s udp connection: %w", action, udpConnErr)
|
||||
case rawConnErr != nil:
|
||||
return fmt.Errorf("%s raw connection: %w", action, rawConnErr)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
@@ -99,8 +100,8 @@ func (s *server) dbStore() (err error) {
|
||||
func writeDB(path string, leases []*Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "writing db: %w") }()
|
||||
|
||||
slices.SortFunc(leases, func(a, b *Lease) bool {
|
||||
return a.Hostname < b.Hostname
|
||||
slices.SortFunc(leases, func(a, b *Lease) (res int) {
|
||||
return strings.Compare(a.Hostname, b.Hostname)
|
||||
})
|
||||
|
||||
dl := &dataLeases{
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"golang.org/x/exp/slices"
|
||||
@@ -31,6 +32,8 @@ const (
|
||||
// Lease contains the necessary information about a DHCP lease. It's used as is
|
||||
// in the database, so don't change it until it's absolutely necessary, see
|
||||
// [dataVersion].
|
||||
//
|
||||
// TODO(e.burkov): Unexport it and use [dhcpsvc.Lease].
|
||||
type Lease struct {
|
||||
// Expiry is the expiration time of the lease.
|
||||
Expiry time.Time `json:"expires"`
|
||||
@@ -153,53 +156,37 @@ const (
|
||||
type Interface interface {
|
||||
Start() (err error)
|
||||
Stop() (err error)
|
||||
|
||||
// Enabled returns true if the DHCP server is running.
|
||||
//
|
||||
// TODO(e.burkov): Currently, we need this method to determine whether the
|
||||
// local domain suffix should be considered while resolving A/AAAA requests.
|
||||
// This is because other parts of the code aren't aware of the DNS suffixes
|
||||
// in DHCP clients names and caller is responsible for trimming it. This
|
||||
// behavior should be changed in the future.
|
||||
Enabled() (ok bool)
|
||||
|
||||
Leases(flags GetLeasesFlags) (leases []*Lease)
|
||||
SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT)
|
||||
FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr)
|
||||
// Leases returns all the leases in the database.
|
||||
Leases() (leases []*dhcpsvc.Lease)
|
||||
|
||||
// MacByIP returns the MAC address of a client with ip. It returns nil if
|
||||
// there is no such client, due to an assumption that a DHCP client must
|
||||
// always have a HardwareAddr.
|
||||
MACByIP(ip netip.Addr) (mac net.HardwareAddr)
|
||||
|
||||
// HostByIP returns the hostname of the DHCP client with the given IP
|
||||
// address. The address will be netip.Addr{} if there is no such client,
|
||||
// due to an assumption that a DHCP client must always have an IP address.
|
||||
HostByIP(ip netip.Addr) (host string)
|
||||
|
||||
// IPByHost returns the IP address of the DHCP client with the given
|
||||
// hostname. The address will be netip.Addr{} if there is no such client,
|
||||
// due to an assumption that a DHCP client must always have an IP address.
|
||||
IPByHost(host string) (ip netip.Addr)
|
||||
|
||||
WriteDiskConfig(c *ServerConfig)
|
||||
}
|
||||
|
||||
// MockInterface is a mock Interface implementation.
|
||||
//
|
||||
// TODO(e.burkov): Move to aghtest when the API stabilized.
|
||||
type MockInterface struct {
|
||||
OnStart func() (err error)
|
||||
OnStop func() (err error)
|
||||
OnEnabled func() (ok bool)
|
||||
OnLeases func(flags GetLeasesFlags) (leases []*Lease)
|
||||
OnSetOnLeaseChanged func(f OnLeaseChangedT)
|
||||
OnFindMACbyIP func(ip netip.Addr) (mac net.HardwareAddr)
|
||||
OnWriteDiskConfig func(c *ServerConfig)
|
||||
}
|
||||
|
||||
var _ Interface = (*MockInterface)(nil)
|
||||
|
||||
// Start implements the Interface for *MockInterface.
|
||||
func (s *MockInterface) Start() (err error) { return s.OnStart() }
|
||||
|
||||
// Stop implements the Interface for *MockInterface.
|
||||
func (s *MockInterface) Stop() (err error) { return s.OnStop() }
|
||||
|
||||
// Enabled implements the Interface for *MockInterface.
|
||||
func (s *MockInterface) Enabled() (ok bool) { return s.OnEnabled() }
|
||||
|
||||
// Leases implements the Interface for *MockInterface.
|
||||
func (s *MockInterface) Leases(flags GetLeasesFlags) (ls []*Lease) { return s.OnLeases(flags) }
|
||||
|
||||
// SetOnLeaseChanged implements the Interface for *MockInterface.
|
||||
func (s *MockInterface) SetOnLeaseChanged(f OnLeaseChangedT) { s.OnSetOnLeaseChanged(f) }
|
||||
|
||||
// FindMACbyIP implements the [Interface] for *MockInterface.
|
||||
func (s *MockInterface) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
return s.OnFindMACbyIP(ip)
|
||||
}
|
||||
|
||||
// WriteDiskConfig implements the Interface for *MockInterface.
|
||||
func (s *MockInterface) WriteDiskConfig(c *ServerConfig) { s.OnWriteDiskConfig(c) }
|
||||
|
||||
// server is the DHCP service that handles DHCPv4, DHCPv6, and HTTP API.
|
||||
type server struct {
|
||||
srv4 DHCPServer
|
||||
@@ -269,7 +256,8 @@ func Create(conf *ServerConfig) (s *server, err error) {
|
||||
}
|
||||
|
||||
// setServers updates DHCPv4 and DHCPv6 servers created from the provided
|
||||
// configuration conf.
|
||||
// configuration conf. It returns the status of both the DHCPv4 and the DHCPv6
|
||||
// servers, which is always false for corresponding server on any error.
|
||||
func (s *server) setServers(conf *ServerConfig) (v4Enabled, v6Enabled bool, err error) {
|
||||
v4conf := conf.Conf4
|
||||
v4conf.InterfaceName = s.conf.InterfaceName
|
||||
@@ -279,7 +267,7 @@ func (s *server) setServers(conf *ServerConfig) (v4Enabled, v6Enabled bool, err
|
||||
s.srv4, err = v4Create(&v4conf)
|
||||
if err != nil {
|
||||
if v4conf.Enabled {
|
||||
return true, false, fmt.Errorf("creating dhcpv4 srv: %w", err)
|
||||
return false, false, fmt.Errorf("creating dhcpv4 srv: %w", err)
|
||||
}
|
||||
|
||||
log.Debug("dhcpd: warning: creating dhcpv4 srv: %s", err)
|
||||
@@ -288,14 +276,11 @@ func (s *server) setServers(conf *ServerConfig) (v4Enabled, v6Enabled bool, err
|
||||
v6conf := conf.Conf6
|
||||
v6conf.InterfaceName = s.conf.InterfaceName
|
||||
v6conf.notify = s.onNotify
|
||||
v6conf.Enabled = s.conf.Enabled
|
||||
if len(v6conf.RangeStart) == 0 {
|
||||
v6conf.Enabled = false
|
||||
}
|
||||
v6conf.Enabled = s.conf.Enabled && len(v6conf.RangeStart) != 0
|
||||
|
||||
s.srv6, err = v6Create(v6conf)
|
||||
if err != nil {
|
||||
return v4conf.Enabled, v6conf.Enabled, fmt.Errorf("creating dhcpv6 srv: %w", err)
|
||||
return v4conf.Enabled, false, fmt.Errorf("creating dhcpv6 srv: %w", err)
|
||||
}
|
||||
|
||||
return v4conf.Enabled, v6conf.Enabled, nil
|
||||
@@ -337,11 +322,6 @@ func (s *server) onNotify(flags uint32) {
|
||||
s.notify(int(flags))
|
||||
}
|
||||
|
||||
// SetOnLeaseChanged - set callback
|
||||
func (s *server) SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT) {
|
||||
s.onLeaseChanged = append(s.onLeaseChanged, onLeaseChanged)
|
||||
}
|
||||
|
||||
func (s *server) notify(flags int) {
|
||||
for _, f := range s.onLeaseChanged {
|
||||
f(flags)
|
||||
@@ -388,15 +368,26 @@ func (s *server) Stop() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Leases returns the list of active IPv4 and IPv6 DHCP leases. It's safe for
|
||||
// concurrent use.
|
||||
func (s *server) Leases(flags GetLeasesFlags) (leases []*Lease) {
|
||||
return append(s.srv4.GetLeases(flags), s.srv6.GetLeases(flags)...)
|
||||
// Leases returns the list of active DHCP leases.
|
||||
func (s *server) Leases() (leases []*dhcpsvc.Lease) {
|
||||
ls := append(s.srv4.GetLeases(LeasesAll), s.srv6.GetLeases(LeasesAll)...)
|
||||
leases = make([]*dhcpsvc.Lease, len(ls))
|
||||
for i, l := range ls {
|
||||
leases[i] = &dhcpsvc.Lease{
|
||||
Expiry: l.Expiry,
|
||||
Hostname: l.Hostname,
|
||||
HWAddr: l.HWAddr,
|
||||
IP: l.IP,
|
||||
IsStatic: l.IsStatic,
|
||||
}
|
||||
}
|
||||
|
||||
return leases
|
||||
}
|
||||
|
||||
// FindMACbyIP returns a MAC address by the IP address of its lease, if there is
|
||||
// MACByIP returns a MAC address by the IP address of its lease, if there is
|
||||
// one.
|
||||
func (s *server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
func (s *server) MACByIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
if ip.Is4() {
|
||||
return s.srv4.FindMACbyIP(ip)
|
||||
}
|
||||
@@ -404,6 +395,24 @@ func (s *server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
return s.srv6.FindMACbyIP(ip)
|
||||
}
|
||||
|
||||
// HostByIP implements the [Interface] interface for *server.
|
||||
//
|
||||
// TODO(e.burkov): Implement this method for DHCPv6.
|
||||
func (s *server) HostByIP(ip netip.Addr) (host string) {
|
||||
if ip.Is4() {
|
||||
return s.srv4.HostByIP(ip)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// IPByHost implements the [Interface] interface for *server.
|
||||
//
|
||||
// TODO(e.burkov): Implement this method for DHCPv6.
|
||||
func (s *server) IPByHost(host string) (ip netip.Addr) {
|
||||
return s.srv4.IPByHost(host)
|
||||
}
|
||||
|
||||
// AddStaticLease - add static v4 lease
|
||||
func (s *server) AddStaticLease(l *Lease) error {
|
||||
return s.srv4.AddStaticLease(l)
|
||||
|
||||
@@ -14,9 +14,11 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type v4ServerConfJSON struct {
|
||||
@@ -75,7 +77,7 @@ type leaseStatic struct {
|
||||
}
|
||||
|
||||
// leasesToStatic converts list of leases to their JSON form.
|
||||
func leasesToStatic(leases []*Lease) (static []*leaseStatic) {
|
||||
func leasesToStatic(leases []*dhcpsvc.Lease) (static []*leaseStatic) {
|
||||
static = make([]*leaseStatic, len(leases))
|
||||
|
||||
for i, l := range leases {
|
||||
@@ -113,7 +115,7 @@ type leaseDynamic struct {
|
||||
}
|
||||
|
||||
// leasesToDynamic converts list of leases to their JSON form.
|
||||
func leasesToDynamic(leases []*Lease) (dynamic []*leaseDynamic) {
|
||||
func leasesToDynamic(leases []*dhcpsvc.Lease) (dynamic []*leaseDynamic) {
|
||||
dynamic = make([]*leaseDynamic, len(leases))
|
||||
|
||||
for i, l := range leases {
|
||||
@@ -143,10 +145,29 @@ func (s *server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) {
|
||||
s.srv4.WriteDiskConfig4(&status.V4)
|
||||
s.srv6.WriteDiskConfig6(&status.V6)
|
||||
|
||||
status.Leases = leasesToDynamic(s.Leases(LeasesDynamic))
|
||||
status.StaticLeases = leasesToStatic(s.Leases(LeasesStatic))
|
||||
leases := s.Leases()
|
||||
slices.SortFunc(leases, func(a, b *dhcpsvc.Lease) (res int) {
|
||||
if a.IsStatic == b.IsStatic {
|
||||
return 0
|
||||
} else if a.IsStatic {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
})
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, status)
|
||||
dynamicIdx := slices.IndexFunc(leases, func(l *dhcpsvc.Lease) (ok bool) {
|
||||
return !l.IsStatic
|
||||
})
|
||||
|
||||
if dynamicIdx == -1 {
|
||||
dynamicIdx = len(leases)
|
||||
}
|
||||
|
||||
status.Leases = leasesToDynamic(leases[dynamicIdx:])
|
||||
status.StaticLeases = leasesToStatic(leases[:dynamicIdx])
|
||||
|
||||
aghhttp.WriteJSONResponseOK(w, r, status)
|
||||
}
|
||||
|
||||
func (s *server) enableDHCP(ifaceName string) (code int, err error) {
|
||||
@@ -395,7 +416,7 @@ func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||
aghhttp.WriteJSONResponseOK(w, r, resp)
|
||||
}
|
||||
|
||||
// newNetInterfaceJSON creates a JSON object from a [net.Interface] iface.
|
||||
@@ -547,7 +568,7 @@ func (s *server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
setOtherDHCPResult(ifaceName, result)
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, result)
|
||||
aghhttp.WriteJSONResponseOK(w, r, result)
|
||||
}
|
||||
|
||||
// setOtherDHCPResult sets the results of the check for another DHCP server in
|
||||
|
||||
@@ -24,7 +24,7 @@ type jsonError struct {
|
||||
// TODO(a.garipov): Either take the logger from the server after we've
|
||||
// refactored logging or make this not a method of *Server.
|
||||
func (s *server) notImplemented(w http.ResponseWriter, r *http.Request) {
|
||||
_ = aghhttp.WriteJSONResponseCode(w, r, http.StatusNotImplemented, &jsonError{
|
||||
aghhttp.WriteJSONResponse(w, r, http.StatusNotImplemented, &jsonError{
|
||||
Message: aghos.Unsupported("dhcp").Error(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ func (winServer) WriteDiskConfig4(_ *V4ServerConf) {}
|
||||
func (winServer) WriteDiskConfig6(_ *V6ServerConf) {}
|
||||
func (winServer) Start() (err error) { return nil }
|
||||
func (winServer) Stop() (err error) { return nil }
|
||||
func (winServer) HostByIP(_ netip.Addr) (host string) { return "" }
|
||||
func (winServer) IPByHost(_ string) (ip netip.Addr) { return netip.Addr{} }
|
||||
|
||||
func v4Create(_ *V4ServerConf) (s DHCPServer, err error) { return winServer{}, nil }
|
||||
func v6Create(_ V6ServerConf) (s DHCPServer, err error) { return winServer{}, nil }
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"github.com/go-ping/ping"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
@@ -46,11 +45,14 @@ type v4Server struct {
|
||||
// leased.
|
||||
leasedOffsets *bitSet
|
||||
|
||||
// leaseHosts is the set of all hostnames of all known DHCP clients.
|
||||
leaseHosts *stringutil.Set
|
||||
|
||||
// leases contains all dynamic and static leases.
|
||||
leases []*Lease
|
||||
|
||||
// hostsIndex is the set of all hostnames of all known DHCP clients.
|
||||
hostsIndex map[string]*Lease
|
||||
|
||||
// ipIndex is an index of leases by their IP addresses.
|
||||
ipIndex map[netip.Addr]*Lease
|
||||
}
|
||||
|
||||
func (s *v4Server) enabled() (ok bool) {
|
||||
@@ -114,6 +116,30 @@ func (s *v4Server) validHostnameForClient(cliHostname string, ip netip.Addr) (ho
|
||||
return hostname
|
||||
}
|
||||
|
||||
// HostByIP implements the [Interface] interface for *v4Server.
|
||||
func (s *v4Server) HostByIP(ip netip.Addr) (host string) {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
if l, ok := s.ipIndex[ip]; ok {
|
||||
return l.Hostname
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// IPByHost implements the [Interface] interface for *v4Server.
|
||||
func (s *v4Server) IPByHost(host string) (ip netip.Addr) {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
if l, ok := s.hostsIndex[host]; ok {
|
||||
return l.IP
|
||||
}
|
||||
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
// ResetLeases resets leases.
|
||||
func (s *v4Server) ResetLeases(leases []*Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "dhcpv4: %w") }()
|
||||
@@ -123,7 +149,8 @@ func (s *v4Server) ResetLeases(leases []*Lease) (err error) {
|
||||
}
|
||||
|
||||
s.leasedOffsets = newBitSet()
|
||||
s.leaseHosts = stringutil.NewSet()
|
||||
s.hostsIndex = make(map[string]*Lease, len(leases))
|
||||
s.ipIndex = make(map[netip.Addr]*Lease, len(leases))
|
||||
s.leases = nil
|
||||
|
||||
for _, l := range leases {
|
||||
@@ -199,20 +226,18 @@ func (s *v4Server) GetLeases(flags GetLeasesFlags) (leases []*Lease) {
|
||||
|
||||
// FindMACbyIP implements the [Interface] for *v4Server.
|
||||
func (s *v4Server) FindMACbyIP(ip netip.Addr) (mac net.HardwareAddr) {
|
||||
if !ip.Is4() {
|
||||
return nil
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
if !ip.Is4() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, l := range s.leases {
|
||||
if l.IP == ip {
|
||||
if l.IsStatic || l.Expiry.After(now) {
|
||||
return l.HWAddr
|
||||
}
|
||||
if l, ok := s.ipIndex[ip]; ok {
|
||||
if l.IsStatic || l.Expiry.After(now) {
|
||||
return l.HWAddr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +274,8 @@ func (s *v4Server) rmLeaseByIndex(i int) {
|
||||
s.leasedOffsets.set(offset, false)
|
||||
}
|
||||
|
||||
s.leaseHosts.Del(l.Hostname)
|
||||
delete(s.hostsIndex, l.Hostname)
|
||||
delete(s.ipIndex, l.IP)
|
||||
|
||||
log.Debug("dhcpv4: removed lease %s (%s)", l.IP, l.HWAddr)
|
||||
}
|
||||
@@ -303,13 +329,15 @@ func (s *v4Server) addLease(l *Lease) (err error) {
|
||||
return fmt.Errorf("lease %s (%s) out of range, not adding", l.IP, l.HWAddr)
|
||||
}
|
||||
|
||||
// TODO(e.burkov): l must have a valid hostname here, investigate.
|
||||
if l.Hostname != "" {
|
||||
if s.leaseHosts.Has(l.Hostname) {
|
||||
if _, ok := s.hostsIndex[l.Hostname]; ok {
|
||||
return ErrDupHostname
|
||||
}
|
||||
|
||||
s.leaseHosts.Add(l.Hostname)
|
||||
s.hostsIndex[l.Hostname] = l
|
||||
}
|
||||
s.ipIndex[l.IP] = l
|
||||
|
||||
s.leases = append(s.leases, l)
|
||||
s.leasedOffsets.set(offset, true)
|
||||
@@ -574,7 +602,7 @@ func (s *v4Server) commitLease(l *Lease, hostname string) {
|
||||
prev := l.Hostname
|
||||
hostname = s.validHostnameForClient(hostname, l.IP)
|
||||
|
||||
if s.leaseHosts.Has(hostname) {
|
||||
if _, ok := s.hostsIndex[hostname]; ok {
|
||||
log.Info("dhcpv4: hostname %q already exists", hostname)
|
||||
|
||||
if prev == "" {
|
||||
@@ -590,11 +618,12 @@ func (s *v4Server) commitLease(l *Lease, hostname string) {
|
||||
|
||||
l.Expiry = time.Now().Add(s.conf.leaseTime)
|
||||
if prev != "" && prev != l.Hostname {
|
||||
s.leaseHosts.Del(prev)
|
||||
delete(s.hostsIndex, prev)
|
||||
}
|
||||
if l.Hostname != "" {
|
||||
s.leaseHosts.Add(l.Hostname)
|
||||
s.hostsIndex[l.Hostname] = l
|
||||
}
|
||||
s.ipIndex[l.IP] = l
|
||||
}
|
||||
|
||||
// allocateLease allocates a new lease for the MAC address. If there are no IP
|
||||
@@ -765,7 +794,7 @@ func (s *v4Server) handleInitReboot(req *dhcpv4.DHCPv4, reqIP net.IP) (l *Lease,
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if !s.conf.subnet.Contains(netip.AddrFrom4(*(*[4]byte)(ip4))) {
|
||||
if !s.conf.subnet.Contains(netip.AddrFrom4([4]byte(ip4))) {
|
||||
// If the DHCP server detects that the client is on the wrong net then
|
||||
// the server SHOULD send a DHCPNAK message to the client.
|
||||
log.Debug("dhcpv4: wrong subnet in init-reboot req msg for %s: %s", mac, reqIP)
|
||||
@@ -1292,7 +1321,8 @@ func (s *v4Server) Stop() (err error) {
|
||||
// Create DHCPv4 server
|
||||
func v4Create(conf *V4ServerConf) (srv *v4Server, err error) {
|
||||
s := &v4Server{
|
||||
leaseHosts: stringutil.NewSet(),
|
||||
hostsIndex: map[string]*Lease{},
|
||||
ipIndex: map[netip.Addr]*Lease{},
|
||||
}
|
||||
|
||||
err = conf.Validate()
|
||||
|
||||
@@ -791,6 +791,14 @@ func TestV4Server_FindMACbyIP(t *testing.T) {
|
||||
IP: anotherIP,
|
||||
}},
|
||||
}
|
||||
s.ipIndex = map[netip.Addr]*Lease{
|
||||
staticIP: s.leases[0],
|
||||
anotherIP: s.leases[1],
|
||||
}
|
||||
s.hostsIndex = map[string]*Lease{
|
||||
staticName: s.leases[0],
|
||||
anotherName: s.leases[1],
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
want net.HardwareAddr
|
||||
@@ -867,7 +875,7 @@ func TestV4Server_handleDecline(t *testing.T) {
|
||||
|
||||
func TestV4Server_handleRelease(t *testing.T) {
|
||||
const (
|
||||
dynamicName = "dymamic-client"
|
||||
dynamicName = "dynamic-client"
|
||||
anotherName = "another-client"
|
||||
)
|
||||
|
||||
|
||||
@@ -26,15 +26,14 @@ const valueIAID = "ADGH" // value for IANA.ID
|
||||
//
|
||||
// TODO(a.garipov): Think about unifying this and v4Server.
|
||||
type v6Server struct {
|
||||
srv *server6.Server
|
||||
leasesLock sync.Mutex
|
||||
leases []*Lease
|
||||
ipAddrs [256]byte
|
||||
sid dhcpv6.DUID
|
||||
|
||||
ra raCtx // RA module
|
||||
|
||||
ra raCtx
|
||||
conf V6ServerConf
|
||||
sid dhcpv6.DUID
|
||||
srv *server6.Server
|
||||
|
||||
leases []*Lease
|
||||
leasesLock sync.Mutex
|
||||
ipAddrs [256]byte
|
||||
}
|
||||
|
||||
// WriteDiskConfig4 - write configuration
|
||||
@@ -59,6 +58,34 @@ func ip6InRange(start, ip net.IP) bool {
|
||||
return start[15] <= ip[15]
|
||||
}
|
||||
|
||||
// HostByIP implements the [Interface] interface for *v6Server.
|
||||
func (s *v6Server) HostByIP(ip netip.Addr) (host string) {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
for _, l := range s.leases {
|
||||
if l.IP == ip {
|
||||
return l.Hostname
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// IPByHost implements the [Interface] interface for *v6Server.
|
||||
func (s *v6Server) IPByHost(host string) (ip netip.Addr) {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
for _, l := range s.leases {
|
||||
if l.Hostname == host {
|
||||
return l.IP
|
||||
}
|
||||
}
|
||||
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
// ResetLeases resets leases.
|
||||
func (s *v6Server) ResetLeases(leases []*Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "dhcpv6: %w") }()
|
||||
|
||||
Reference in New Issue
Block a user