mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2026-11-03 02:27:04 +09:00
Pull request 2539: 4923-gopacket-dhcp-vol.15
Squashed commit of the following: commit 82c44225c770f7379ce0f11a80bc9f5ca6ffdc77 Merge: 4c8ea9b3c9815e5386Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Dec 4 22:03:50 2025 +0300 Merge branch 'master' into 4923-gopacket-dhcp-vol.15 commit 4c8ea9b3cd8275c59dbf5a23c80e182f42089c5f Merge: 96ef5241d8dd4962d4Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Dec 4 20:26:13 2025 +0300 Merge branch 'master' into 4923-gopacket-dhcp-vol.15 commit 96ef5241ddfee692a1696545e822916e7bb62270 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Dec 4 20:24:28 2025 +0300 dhcpsvc: imp docs commit 340037c2561f421f7ea04684a89dfd4ae8f952b7 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Wed Dec 3 14:54:20 2025 +0300 dhcpsvc: imp code, docs commit 94fa72beb9692f67dd7b8e2e82e871d3a2f810f4 Merge: ffad4c7c46404150eaAuthor: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Wed Dec 3 14:53:17 2025 +0300 Merge branch 'master' into 4923-gopacket-dhcp-vol.15 commit ffad4c7c415c1313ec74e1a7597b50e69559bcb5 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Dec 2 15:20:47 2025 +0300 dhcpsvc: refactor network devices
This commit is contained in:
1 parent
9815e53863
commit
72cb04c51e
7 files changed
+444
-407
No files matched your search
@@ -20,6 +20,12 @@ type Config struct {
|
||||
// contain valid interface names and configurations.
|
||||
Interfaces map[string]*InterfaceConfig
|
||||
|
||||
// NetworkDeviceManager is the manager of network devices. It must not be
|
||||
// nil.
|
||||
//
|
||||
// TODO(e.burkov): Set.
|
||||
NetworkDeviceManager NetworkDeviceManager
|
||||
|
||||
// Logger will be used to log the DHCP events. It must not be nil.
|
||||
Logger *slog.Logger
|
||||
|
||||
|
||||
+21
-33
@@ -4,28 +4,19 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
// responseWriter4 writes DHCPv4 response to the client.
|
||||
type responseWriter4 interface {
|
||||
// write writes the DHCPv4 response to the client.
|
||||
write(ctx context.Context, pkt *layers.DHCPv4) (err error)
|
||||
}
|
||||
|
||||
// responseWriter6 writes DHCPv6 response to the client.
|
||||
type responseWriter6 interface {
|
||||
// write writes the DHCPv6 response to the client.
|
||||
write(ctx context.Context, pkt *layers.DHCPv6) (err error)
|
||||
}
|
||||
|
||||
// serve handles the incoming packets and dispatches them to the appropriate
|
||||
// handler based on the Ethernet type. It's used to run in a separate goroutine
|
||||
// as it blocks until packets channel is closed.
|
||||
func (srv *DHCPServer) serve(ctx context.Context) {
|
||||
// serveEther4 handles the incoming ethernet packets and dispatches them to the
|
||||
// appropriate handler. It's used to run in a separate goroutine as it blocks
|
||||
// until packets channel is closed. iface and nd must not be nil.
|
||||
func (srv *DHCPServer) serveEther4(ctx context.Context, iface *dhcpInterfaceV4, nd NetworkDevice) {
|
||||
defer slogutil.RecoverAndLog(ctx, srv.logger)
|
||||
|
||||
for pkt := range srv.packetSource.Packets() {
|
||||
src := gopacket.NewPacketSource(nd, nd.LinkType())
|
||||
|
||||
for pkt := range src.Packets() {
|
||||
etherLayer, ok := pkt.Layer(layers.LayerTypeEthernet).(*layers.Ethernet)
|
||||
if !ok {
|
||||
actual := pkt.Layers()
|
||||
@@ -34,28 +25,25 @@ func (srv *DHCPServer) serve(ctx context.Context) {
|
||||
continue
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
switch typ := etherLayer.EthernetType; typ {
|
||||
case layers.EthernetTypeIPv4:
|
||||
// TODO(e.burkov): Set the response writer.
|
||||
var rw responseWriter4
|
||||
err = srv.serveV4(ctx, rw, pkt)
|
||||
case layers.EthernetTypeIPv6:
|
||||
// TODO(e.burkov): Set the response writer.
|
||||
var rw responseWriter6
|
||||
err = srv.serveV6(ctx, rw, pkt)
|
||||
default:
|
||||
// TODO(e.burkov): It seems, there is another standard for Ethernet
|
||||
// header, which uses the Length field instead of the EthernetType,
|
||||
// so handle it properly.
|
||||
srv.logger.DebugContext(ctx, "skipping ethernet packet", "type", typ)
|
||||
ipLayer, ok := pkt.Layer(layers.LayerTypeIPv4).(*layers.IPv4)
|
||||
if !ok {
|
||||
actual := pkt.Layers()
|
||||
srv.logger.DebugContext(ctx, "skipping non-ipv4 packet", "layers", actual)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
fd := &frameData{
|
||||
ether: etherLayer,
|
||||
ip: ipLayer,
|
||||
device: nd,
|
||||
}
|
||||
|
||||
err := srv.serveV4(ctx, iface, pkt, fd)
|
||||
if err != nil {
|
||||
srv.logger.ErrorContext(ctx, "serving", slogutil.KeyError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Add DHCPServer.serveEther6.
|
||||
+290
-53
@@ -6,16 +6,18 @@ import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
// serveV4 handles the ethernet packet of IPv4 type. rw and pkt must not be
|
||||
// nil.
|
||||
// serveV4 handles the ethernet packet of IPv4 type. iface and pkt must not be
|
||||
// nil. iface and fd must not be nil. pkt must be an IPv4 packet.
|
||||
func (srv *DHCPServer) serveV4(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
iface *dhcpInterfaceV4,
|
||||
pkt gopacket.Packet,
|
||||
fd *frameData,
|
||||
) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "serving dhcpv4: %w") }()
|
||||
|
||||
@@ -42,37 +44,36 @@ func (srv *DHCPServer) serveV4(
|
||||
// message.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc2131#section-3.
|
||||
return fmt.Errorf("dhcpv4: message type: %w", errors.ErrNoValue)
|
||||
return fmt.Errorf("message type: %w", errors.ErrNoValue)
|
||||
}
|
||||
|
||||
return srv.handleDHCPv4(ctx, rw, typ, req)
|
||||
return iface.handleDHCPv4(ctx, typ, req, fd)
|
||||
}
|
||||
|
||||
// handleDHCPv4 handles the DHCPv4 message of the given type. The DHCPDISCOVER
|
||||
// messages are handled by all interfaces concurrently, as those offer addresses
|
||||
// for the independent networks. The DHCPREQUEST, DHCPRELEASE, and DHCPDECLINE
|
||||
// messages are handled by the server itself as it should pick the appropriate
|
||||
// interface according to the client's choice. req must not be nil, typ should
|
||||
// be one of:
|
||||
// messages are handled by the appropriate interface according to the client's
|
||||
// choice. req and fd must not be nil, typ should be one of:
|
||||
// - [layers.DHCPMsgTypeDiscover]
|
||||
// - [layers.DHCPMsgTypeRequest]
|
||||
// - [layers.DHCPMsgTypeRelease]
|
||||
// - [layers.DHCPMsgTypeDecline]
|
||||
func (srv *DHCPServer) handleDHCPv4(
|
||||
func (iface *dhcpInterfaceV4) handleDHCPv4(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
typ layers.DHCPMsgType,
|
||||
req *layers.DHCPv4,
|
||||
fd *frameData,
|
||||
) (err error) {
|
||||
switch typ {
|
||||
case layers.DHCPMsgTypeDiscover:
|
||||
srv.handleDiscover(ctx, rw, req)
|
||||
iface.handleDiscover(ctx, req, fd)
|
||||
case layers.DHCPMsgTypeRequest:
|
||||
srv.handleRequest(ctx, rw, req)
|
||||
iface.handleRequest(ctx, req, fd)
|
||||
case layers.DHCPMsgTypeRelease:
|
||||
srv.handleRelease(ctx, req)
|
||||
iface.handleRelease(ctx, req)
|
||||
case layers.DHCPMsgTypeDecline:
|
||||
srv.handleDecline(ctx, req)
|
||||
iface.handleDecline(ctx, req)
|
||||
default:
|
||||
// TODO(e.burkov): Handle DHCPINFORM.
|
||||
return fmt.Errorf("dhcpv4: request type: %w: %v", errors.ErrBadEnumValue, typ)
|
||||
@@ -81,98 +82,334 @@ func (srv *DHCPServer) handleDHCPv4(
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleDiscover handles the DHCPv4 message of DHCPDISCOVER type. rw must not
|
||||
// be nil, req must be a DHCPDISCOVER message.
|
||||
func (srv *DHCPServer) handleDiscover(ctx context.Context, rw responseWriter4, req *layers.DHCPv4) {
|
||||
// TODO(e.burkov): Check existing leases, either allocated or offered.
|
||||
|
||||
for _, iface := range srv.interfaces4 {
|
||||
go iface.handleDiscover(ctx, rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
// handleRequest handles the DHCPv4 message of DHCPREQUEST type. rw must not be
|
||||
// nil, req must be a DHCPREQUEST message.
|
||||
// handleRequest handles the DHCPv4 message of DHCPREQUEST type. req must be a
|
||||
// DHCPREQUEST message. req and fd must not be nil.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc2131#section-4.3.2.
|
||||
//
|
||||
// TODO(e.burkov): Remove allocated leases after client have chosen one.
|
||||
func (srv *DHCPServer) handleRequest(ctx context.Context, rw responseWriter4, req *layers.DHCPv4) {
|
||||
func (iface *dhcpInterfaceV4) handleRequest(
|
||||
ctx context.Context,
|
||||
req *layers.DHCPv4,
|
||||
fd *frameData,
|
||||
) {
|
||||
srvID, hasSrvID := serverID4(req)
|
||||
reqIP, hasReqIP := requestedIPv4(req)
|
||||
|
||||
l := iface.common.logger
|
||||
|
||||
switch {
|
||||
case hasSrvID && !srvID.IsUnspecified():
|
||||
// If the DHCPREQUEST message contains a server identifier option, the
|
||||
// message is in response to a DHCPOFFER message. Otherwise, the
|
||||
// message is a request to verify or extend an existing lease.
|
||||
iface, ok := srv.interfaces4.findInterface(srvID)
|
||||
if !ok {
|
||||
srv.logger.DebugContext(ctx, "skipping selecting request", "serverid", srvID)
|
||||
if !iface.subnet.Contains(srvID) {
|
||||
l.DebugContext(ctx, "skipping selecting request", "serverid", srvID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
iface.handleSelecting(ctx, rw, req, reqIP)
|
||||
iface.handleSelecting(ctx, req, fd, reqIP)
|
||||
case hasReqIP && !reqIP.IsUnspecified():
|
||||
// Requested IP address option MUST be filled in with client's notion of
|
||||
// its previously assigned address.
|
||||
iface, ok := srv.interfaces4.findInterface(reqIP)
|
||||
if !ok {
|
||||
if !iface.subnet.Contains(reqIP) {
|
||||
// If the DHCP server detects that the client is on the wrong net
|
||||
// then the server SHOULD send a DHCPNAK message to the client.
|
||||
srv.logger.DebugContext(ctx, "declining init-reboot request", "requestedip", reqIP)
|
||||
iface.respondNAK(ctx, rw, req)
|
||||
l.DebugContext(ctx, "declining init-reboot request", "requestedip", reqIP)
|
||||
iface.respondNAK(ctx, req, fd)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
iface.handleInitReboot(ctx, rw, req, reqIP)
|
||||
iface.handleInitReboot(ctx, req, fd, reqIP)
|
||||
default:
|
||||
// Server identifier MUST NOT be filled in, requested IP address option
|
||||
// MUST NOT be filled in.
|
||||
ip, _ := netip.AddrFromSlice(req.ClientIP.To4())
|
||||
iface, ok := srv.interfaces4.findInterface(ip)
|
||||
if !ok {
|
||||
srv.logger.DebugContext(ctx, "skipping renew request", "clientip", ip)
|
||||
if !iface.subnet.Contains(ip) {
|
||||
l.DebugContext(ctx, "skipping renew request", "clientip", ip)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
iface.handleRenew(ctx, rw, req, ip)
|
||||
iface.handleRenew(ctx, req, fd, ip)
|
||||
}
|
||||
}
|
||||
|
||||
// handleDecline handles the DHCPv4 message of DHCPDECLINE type. req must be a
|
||||
// handleDiscover handles messages of type DHCPDISCOVER. req must be a
|
||||
// DHCPDISCOVER message, fd must not be nil.
|
||||
func (iface *dhcpInterfaceV4) handleDiscover(
|
||||
ctx context.Context,
|
||||
req *layers.DHCPv4,
|
||||
fd *frameData,
|
||||
) {
|
||||
l := iface.common.logger
|
||||
mac := req.ClientHWAddr
|
||||
mk := macToKey(mac)
|
||||
|
||||
// Check if there's an existing lease for this MAC address.
|
||||
iface.common.indexMu.Lock()
|
||||
defer iface.common.indexMu.Unlock()
|
||||
|
||||
lease, hasLease := iface.common.leases[mk]
|
||||
if hasLease {
|
||||
reqIP, hasReqIP := requestedIPv4(req)
|
||||
if hasReqIP && reqIP != lease.IP {
|
||||
l.DebugContext(ctx, "different requested ip", "requested", reqIP, "lease", lease.IP)
|
||||
}
|
||||
|
||||
iface.respondOffer(ctx, req, fd, lease)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Allocate a new lease.
|
||||
lease, err := iface.allocateLease(ctx, mac)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "allocating a lease", "error", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Send DHCPOFFER with new lease.
|
||||
iface.respondOffer(ctx, req, fd, lease)
|
||||
}
|
||||
|
||||
// handleSelecting handles messages of type DHCPREQUEST in SELECTING state. req
|
||||
// must be a DHCPREQUEST message, reqIP must be a valid IPv4 address, fd must
|
||||
// not be nil.
|
||||
func (iface *dhcpInterfaceV4) handleSelecting(
|
||||
ctx context.Context,
|
||||
req *layers.DHCPv4,
|
||||
fd *frameData,
|
||||
reqIP netip.Addr,
|
||||
) {
|
||||
l := iface.common.logger
|
||||
|
||||
if !reqIP.Is4() {
|
||||
l.DebugContext(ctx, "bad requested address", "requestedip", reqIP)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ciaddr, ok := netip.AddrFromSlice(req.ClientIP)
|
||||
if ok && !ciaddr.IsUnspecified() {
|
||||
l.DebugContext(ctx, "non-zero ciaddr in selecting request", "ciaddr", ciaddr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
mac := req.ClientHWAddr
|
||||
mk := macToKey(mac)
|
||||
|
||||
iface.common.indexMu.Lock()
|
||||
defer iface.common.indexMu.Unlock()
|
||||
|
||||
lease, hasLease := iface.common.leases[mk]
|
||||
if !hasLease {
|
||||
l.DebugContext(ctx, "no reserved lease", "clienthwaddr", mac)
|
||||
iface.respondNAK(ctx, req, fd)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if lease.IP != reqIP {
|
||||
l.DebugContext(ctx, "selecting request mismatched", "requested", reqIP, "lease", lease.IP)
|
||||
iface.respondNAK(ctx, req, fd)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Commit the lease and send ACK.
|
||||
iface.commitLease(ctx, lease, hostname4(req))
|
||||
iface.respondACK(ctx, req, fd, lease)
|
||||
}
|
||||
|
||||
// handleInitReboot handles messages of type DHCPREQUEST in INIT-REBOOT state.
|
||||
// req must be a DHCPREQUEST message, reqIP must be a valid IPv4 address, fd
|
||||
// must not be nil.
|
||||
func (iface *dhcpInterfaceV4) handleInitReboot(
|
||||
ctx context.Context,
|
||||
req *layers.DHCPv4,
|
||||
fd *frameData,
|
||||
reqIP netip.Addr,
|
||||
) {
|
||||
l := iface.common.logger
|
||||
|
||||
if !reqIP.Is4() {
|
||||
l.DebugContext(ctx, "bad requested address", "requestedip", reqIP)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ciaddr must be zero. The client is seeking to verify a previously
|
||||
// allocated, cached configuration.
|
||||
ciaddr, _ := netip.AddrFromSlice(req.ClientIP)
|
||||
if ciaddr.IsValid() && !ciaddr.IsUnspecified() {
|
||||
l.DebugContext(ctx, "unexpected ciaddr in init-reboot request", "ciaddr", ciaddr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the lease exists and matches.
|
||||
mac := req.ClientHWAddr
|
||||
mk := macToKey(mac)
|
||||
|
||||
iface.common.indexMu.Lock()
|
||||
defer iface.common.indexMu.Unlock()
|
||||
|
||||
lease, hasLease := iface.common.leases[mk]
|
||||
if !hasLease {
|
||||
// If the DHCP server has no record of this client, then it MUST remain
|
||||
// silent, and MAY output a warning to the network administrator.
|
||||
l.WarnContext(ctx, "no existing lease", "mac", mac)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if lease.IP != reqIP {
|
||||
l.WarnContext(ctx, "init-reboot request mismatched", "requested", reqIP, "lease", lease.IP)
|
||||
iface.respondNAK(ctx, req, fd)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Commit the lease and send ACK.
|
||||
iface.commitLease(ctx, lease, hostname4(req))
|
||||
iface.respondACK(ctx, req, fd, lease)
|
||||
}
|
||||
|
||||
// handleRenew handles messages of type DHCPREQUEST in RENEWING or REBINDING
|
||||
// state. req must be a DHCPREQUEST message, ip should be a previously leased
|
||||
// address, fd must not be nil.
|
||||
func (iface *dhcpInterfaceV4) handleRenew(
|
||||
ctx context.Context,
|
||||
req *layers.DHCPv4,
|
||||
fd *frameData,
|
||||
ip netip.Addr,
|
||||
) {
|
||||
l := iface.common.logger
|
||||
|
||||
// Check if the lease exists and matches.
|
||||
mac := req.ClientHWAddr
|
||||
mk := macToKey(mac)
|
||||
|
||||
iface.common.indexMu.Lock()
|
||||
defer iface.common.indexMu.Unlock()
|
||||
|
||||
lease, hasLease := iface.common.leases[mk]
|
||||
if !hasLease {
|
||||
// If the DHCP server has no record of this client, then it MUST remain
|
||||
// silent, and MAY output a warning to the network administrator.
|
||||
l.InfoContext(ctx, "no existing lease", "mac", mac)
|
||||
|
||||
// TODO(e.burkov): Investigate if we should respond with NAK.
|
||||
return
|
||||
}
|
||||
|
||||
if lease.IP != ip {
|
||||
l.DebugContext(ctx, "renew request mismatched", "ciaddr", ip, "lease", lease.IP)
|
||||
iface.respondNAK(ctx, req, fd)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Commit the lease and send ACK.
|
||||
iface.commitLease(ctx, lease, hostname4(req))
|
||||
iface.respondACK(ctx, req, fd, lease)
|
||||
}
|
||||
|
||||
// handleDecline handles messages of type DHCPDECLINE. req must be a
|
||||
// DHCPDECLINE message.
|
||||
func (srv *DHCPServer) handleDecline(ctx context.Context, req *layers.DHCPv4) {
|
||||
//
|
||||
// TODO(e.burkov): Log the message option, as the request should include one.
|
||||
//
|
||||
// TODO(e.burkov): Consider DRY'ing this with [dhcpInterfaceV4.handleRelease].
|
||||
func (iface *dhcpInterfaceV4) handleDecline(ctx context.Context, req *layers.DHCPv4) {
|
||||
l := iface.common.logger
|
||||
|
||||
reqIP, hasReqIP := requestedIPv4(req)
|
||||
if !hasReqIP {
|
||||
srv.logger.DebugContext(ctx, "skipping decline message without requested ip")
|
||||
l.DebugContext(ctx, "skipping decline message without requested ip")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
iface, ok := srv.interfaces4.findInterface(reqIP)
|
||||
if !ok {
|
||||
srv.logger.DebugContext(ctx, "skipping decline message", "requestedip", reqIP)
|
||||
if !iface.subnet.Contains(reqIP) {
|
||||
l.DebugContext(ctx, "skipping decline message", "requestedip", reqIP)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
iface.handleDecline(ctx, reqIP, req)
|
||||
// Check if the lease exists and matches.
|
||||
mac := req.ClientHWAddr
|
||||
mk := macToKey(mac)
|
||||
|
||||
iface.common.indexMu.Lock()
|
||||
defer iface.common.indexMu.Unlock()
|
||||
|
||||
lease, hasLease := iface.common.leases[mk]
|
||||
if !hasLease {
|
||||
l.ErrorContext(ctx, "decline message for non-existing lease", "mac", mac)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if lease.IP != reqIP {
|
||||
l.ErrorContext(ctx, "decline mismatch", "ip", reqIP, "lease", lease.IP)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
l.WarnContext(ctx, "lease reported to be unavailable", "ip", lease.IP)
|
||||
|
||||
err := iface.common.index.remove(ctx, l, lease, iface.common)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "removing lease", slogutil.KeyError, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
iface.common.blockLease(lease, iface.clock)
|
||||
}
|
||||
|
||||
// handleRelease handles the DHCPv4 message of DHCPRELEASE type. req must be a
|
||||
// handleRelease handles messages of type DHCPRELEASE. req must be a
|
||||
// DHCPRELEASE message.
|
||||
func (srv *DHCPServer) handleRelease(ctx context.Context, req *layers.DHCPv4) {
|
||||
func (iface *dhcpInterfaceV4) handleRelease(ctx context.Context, req *layers.DHCPv4) {
|
||||
l := iface.common.logger
|
||||
|
||||
ip, _ := netip.AddrFromSlice(req.ClientIP.To4())
|
||||
iface, ok := srv.interfaces4.findInterface(ip)
|
||||
if !ok {
|
||||
srv.logger.DebugContext(ctx, "skipping release message", "clientip", ip)
|
||||
if !iface.subnet.Contains(ip) {
|
||||
l.DebugContext(ctx, "skipping release message", "clientip", ip)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
iface.handleRelease(ctx, ip, req)
|
||||
// Check if the lease exists and matches.
|
||||
mac := req.ClientHWAddr
|
||||
mk := macToKey(mac)
|
||||
|
||||
iface.common.indexMu.Lock()
|
||||
defer iface.common.indexMu.Unlock()
|
||||
|
||||
lease, hasLease := iface.common.leases[mk]
|
||||
if !hasLease {
|
||||
l.WarnContext(ctx, "release message for non-existing lease", "mac", mac)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if lease.IP != ip {
|
||||
l.WarnContext(ctx, "release mismatch", "ip", ip, "lease", lease.IP)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err := iface.common.index.remove(ctx, l, lease, iface.common)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "removing lease", slogutil.KeyError, err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,13 @@ import (
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
// serveV6 handles the ethernet packet of IPv6 type. rw and pkt must not be
|
||||
// nil.
|
||||
// serveV6 handles the ethernet packet of IPv6 type. iface and pkt must not be
|
||||
// nil. ctx must contain a [frameData] accessible with [frameDataFromContext].
|
||||
//
|
||||
//lint:ignore U1000 TODO(e.burkov): Use.
|
||||
func (srv *DHCPServer) serveV6(
|
||||
ctx context.Context,
|
||||
rw responseWriter6,
|
||||
_ *dhcpInterfaceV6,
|
||||
pkt gopacket.Packet,
|
||||
) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "serving dhcpv6: %w") }()
|
||||
@@ -29,13 +31,12 @@ func (srv *DHCPServer) serveV6(
|
||||
|
||||
// TODO(e.burkov): Handle duplicate TransactionID.
|
||||
|
||||
return srv.handleDHCPv6(ctx, rw, msg.MsgType, msg)
|
||||
return srv.handleDHCPv6(ctx, msg.MsgType, msg)
|
||||
}
|
||||
|
||||
// handleDHCPv6 handles the DHCPv6 message of the given type.
|
||||
func (srv *DHCPServer) handleDHCPv6(
|
||||
_ context.Context,
|
||||
_ responseWriter6,
|
||||
typ layers.DHCPv6MsgType,
|
||||
_ *layers.DHCPv6,
|
||||
) (err error) {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/validate"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
// NetworkDeviceConfig is the configuration for a network device.
|
||||
type NetworkDeviceConfig struct {
|
||||
// Name is the name of the network device. It must be a valid interface
|
||||
// name on the system.
|
||||
Name string
|
||||
}
|
||||
|
||||
// Validate implements the [validate.Interface] interface for
|
||||
// *NetworkDeviceConfig.
|
||||
func (conf *NetworkDeviceConfig) Validate() (err error) {
|
||||
if conf == nil {
|
||||
return errors.ErrNoValue
|
||||
}
|
||||
|
||||
return validate.NotEmpty("Name", conf.Name)
|
||||
}
|
||||
|
||||
// NetworkDeviceManager creates and manages network devices.
|
||||
//
|
||||
// TODO(e.burkov): Add device closing method.
|
||||
type NetworkDeviceManager interface {
|
||||
// Open opens a network device. conf must be valid.
|
||||
Open(ctx context.Context, conf *NetworkDeviceConfig) (dev NetworkDevice, err error)
|
||||
}
|
||||
|
||||
// NetworkDevice provides reading and writing packets to a network interface.
|
||||
type NetworkDevice interface {
|
||||
gopacket.PacketDataSource
|
||||
|
||||
// LinkType returns the link type of the network interface.
|
||||
LinkType() (lt layers.LinkType)
|
||||
|
||||
// WritePacketData writes a serialized packet to the network interface.
|
||||
WritePacketData(data []byte) (err error)
|
||||
}
|
||||
|
||||
// frameData stores the Ethernet and IPv4 layers of the incoming packet, and
|
||||
// the network device that the packet was received from.
|
||||
type frameData struct {
|
||||
ether *layers.Ethernet
|
||||
ip *layers.IPv4
|
||||
device NetworkDevice
|
||||
}
|
||||
+26
-14
@@ -14,7 +14,6 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/google/gopacket"
|
||||
)
|
||||
|
||||
// DHCPServer is a DHCP server for both IPv4 and IPv6 address families.
|
||||
@@ -28,10 +27,8 @@ type DHCPServer struct {
|
||||
// logger logs common DHCP events.
|
||||
logger *slog.Logger
|
||||
|
||||
// packetSource is the source of DHCP packets to process.
|
||||
//
|
||||
// TODO(e.burkov): Implement and set.
|
||||
packetSource gopacket.PacketSource
|
||||
// deviceManager is the manager of network devices.
|
||||
deviceManager NetworkDeviceManager
|
||||
|
||||
// localTLD is the top-level domain name to use for resolving DHCP clients'
|
||||
// hostnames.
|
||||
@@ -70,12 +67,13 @@ func New(ctx context.Context, conf *Config) (srv *DHCPServer, err error) {
|
||||
enabled.Store(conf.Enabled)
|
||||
|
||||
srv = &DHCPServer{
|
||||
enabled: enabled,
|
||||
logger: l,
|
||||
localTLD: conf.LocalDomainName,
|
||||
leasesMu: &sync.RWMutex{},
|
||||
leases: newLeaseIndex(conf.DBFilePath),
|
||||
icmpTimeout: conf.ICMPTimeout,
|
||||
enabled: enabled,
|
||||
logger: l,
|
||||
deviceManager: conf.NetworkDeviceManager,
|
||||
localTLD: conf.LocalDomainName,
|
||||
leasesMu: &sync.RWMutex{},
|
||||
leases: newLeaseIndex(conf.DBFilePath),
|
||||
icmpTimeout: conf.ICMPTimeout,
|
||||
}
|
||||
|
||||
srv.interfaces4, srv.interfaces6 = srv.newInterfaces(ctx, l, conf.Interfaces)
|
||||
@@ -137,13 +135,27 @@ func (srv *DHCPServer) newInterfaces(
|
||||
func (srv *DHCPServer) Start(ctx context.Context) (err error) {
|
||||
srv.logger.DebugContext(ctx, "starting dhcp server")
|
||||
|
||||
// TODO(e.burkov): Listen to configured interfaces.
|
||||
var errs []error
|
||||
for _, iface := range srv.interfaces4 {
|
||||
var nd NetworkDevice
|
||||
nd, err = srv.deviceManager.Open(ctx, &NetworkDeviceConfig{
|
||||
Name: iface.common.name,
|
||||
})
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
|
||||
go srv.serve(context.WithoutCancel(ctx))
|
||||
continue
|
||||
}
|
||||
|
||||
return nil
|
||||
go srv.serveEther4(context.WithoutCancel(ctx), iface, nd)
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Serve EthernetTypeIPv6.
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// Shutdown implements the [Interface] interface for *DHCPServer.
|
||||
func (srv *DHCPServer) Shutdown(ctx context.Context) (err error) {
|
||||
srv.logger.DebugContext(ctx, "shutting down dhcp server")
|
||||
|
||||
|
||||
+41
-302
@@ -10,9 +10,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"github.com/AdguardTeam/golibs/validate"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
@@ -198,36 +198,37 @@ func (srv *DHCPServer) newDHCPInterfaceV4(
|
||||
}
|
||||
|
||||
// commitLease updates the lease in database, using new hostname if it's valid.
|
||||
func (iface *dhcpInterfaceV4) commitLease(ctx context.Context, l *Lease, hostname string) {
|
||||
// TODO(e.burkov): Implement.
|
||||
}
|
||||
//
|
||||
// TODO(e.burkov): Implement.
|
||||
func (iface *dhcpInterfaceV4) commitLease(ctx context.Context, l *Lease, hostname string) {}
|
||||
|
||||
// respondOffer sends a DHCPOFFER message to the client. rw, req, and l must
|
||||
// respondOffer sends a DHCPOFFER message to the client. req, fd, and l must
|
||||
// not be nil.
|
||||
func (iface *dhcpInterfaceV4) respondOffer(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
req *layers.DHCPv4,
|
||||
fd *frameData,
|
||||
l *Lease,
|
||||
) {
|
||||
resp := iface.buildResponse(req, l, layers.DHCPMsgTypeOffer)
|
||||
if err := rw.write(ctx, resp); err != nil {
|
||||
if err := respond4(fd, resp); err != nil {
|
||||
iface.common.logger.ErrorContext(ctx, "writing offer", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// respondACK sends a DHCPACK message to the client.
|
||||
// respondACK sends a DHCPACK message to the client. req, fd, and l must not be
|
||||
// nil.
|
||||
//
|
||||
// TODO(e.burkov): Implement according to RFC, answer to DHCPINFORM
|
||||
// differently, when it's supported.
|
||||
func (iface *dhcpInterfaceV4) respondACK(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
req *layers.DHCPv4,
|
||||
fd *frameData,
|
||||
l *Lease,
|
||||
) {
|
||||
resp := iface.buildResponse(req, l, layers.DHCPMsgTypeAck)
|
||||
if err := rw.write(ctx, resp); err != nil {
|
||||
if err := respond4(fd, resp); err != nil {
|
||||
iface.common.logger.ErrorContext(ctx, "writing ack", "error", err)
|
||||
}
|
||||
}
|
||||
@@ -238,14 +239,14 @@ var v4OptionMessageTypeNAK = layers.NewDHCPOption(
|
||||
[]byte{byte(layers.DHCPMsgTypeNak)},
|
||||
)
|
||||
|
||||
// respondNAK constructs and sends a DHCPNAK message to the client. rw and req
|
||||
// must not be nil.
|
||||
// respondNAK constructs and sends a DHCPNAK message to the client. req, fd,
|
||||
// and resp must not be nil.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc2131#section-4.3.1.
|
||||
func (iface *dhcpInterfaceV4) respondNAK(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
req *layers.DHCPv4,
|
||||
fd *frameData,
|
||||
) {
|
||||
resp := &layers.DHCPv4{
|
||||
Operation: layers.DHCPOpReply,
|
||||
@@ -261,7 +262,7 @@ func (iface *dhcpInterfaceV4) respondNAK(
|
||||
},
|
||||
}
|
||||
|
||||
if err := rw.write(ctx, resp); err != nil {
|
||||
if err := respond4(fd, resp); err != nil {
|
||||
iface.common.logger.ErrorContext(ctx, "writing nak", "error", err)
|
||||
}
|
||||
}
|
||||
@@ -307,281 +308,6 @@ func (iface *dhcpInterfaceV4) buildResponse(
|
||||
return resp
|
||||
}
|
||||
|
||||
// handleDiscover handles messages of type DHCPDISCOVER. rw and req must not be
|
||||
// nil.
|
||||
func (iface *dhcpInterfaceV4) handleDiscover(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
req *layers.DHCPv4,
|
||||
) {
|
||||
l := iface.common.logger
|
||||
mac := req.ClientHWAddr
|
||||
mk := macToKey(mac)
|
||||
|
||||
// Check if there's an existing lease for this MAC address.
|
||||
iface.common.indexMu.Lock()
|
||||
defer iface.common.indexMu.Unlock()
|
||||
|
||||
lease, hasLease := iface.common.leases[mk]
|
||||
if hasLease {
|
||||
reqIP, hasReqIP := requestedIPv4(req)
|
||||
if hasReqIP && reqIP != lease.IP {
|
||||
l.DebugContext(ctx, "different requested ip", "requested", reqIP, "lease", lease.IP)
|
||||
}
|
||||
|
||||
iface.respondOffer(ctx, rw, req, lease)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Allocate a new lease.
|
||||
lease, err := iface.allocateLease(ctx, mac)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "allocating a lease", "error", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Send DHCPOFFER with new lease.
|
||||
iface.respondOffer(ctx, rw, req, lease)
|
||||
}
|
||||
|
||||
// handleSelecting handles messages of type DHCPREQUEST in SELECTING state. req
|
||||
// must contain a server identifier option that matches the iface's subnet, and
|
||||
// client IP address must be empty or unspecified, and requested IP address
|
||||
// must be filled in with the yiaddr value from the chosen DHCPOFFER. rw must
|
||||
// not be nil, reqIP must be a valid IPv4 address.
|
||||
func (iface *dhcpInterfaceV4) handleSelecting(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
req *layers.DHCPv4,
|
||||
reqIP netip.Addr,
|
||||
) {
|
||||
l := iface.common.logger
|
||||
|
||||
if !reqIP.Is4() {
|
||||
l.DebugContext(ctx, "bad requested address", "requestedip", reqIP)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ciaddr, ok := netip.AddrFromSlice(req.ClientIP)
|
||||
if ok && !ciaddr.IsUnspecified() {
|
||||
l.DebugContext(ctx, "non-zero ciaddr in selecting request", "ciaddr", ciaddr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
mac := req.ClientHWAddr
|
||||
mk := macToKey(mac)
|
||||
|
||||
iface.common.indexMu.Lock()
|
||||
defer iface.common.indexMu.Unlock()
|
||||
|
||||
lease, hasLease := iface.common.leases[mk]
|
||||
if !hasLease {
|
||||
l.DebugContext(ctx, "no reserved lease", "clienthwaddr", mac)
|
||||
iface.respondNAK(ctx, rw, req)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if lease.IP != reqIP {
|
||||
l.DebugContext(ctx, "selecting request mismatched", "requested", reqIP, "lease", lease.IP)
|
||||
iface.respondNAK(ctx, rw, req)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Commit the lease and send ACK.
|
||||
iface.commitLease(ctx, lease, hostname4(req))
|
||||
iface.respondACK(ctx, rw, req, lease)
|
||||
}
|
||||
|
||||
// handleInitReboot handles messages of type DHCPREQUEST in INIT-REBOOT state.
|
||||
// req must contain a client IP address option that matches the iface's subnet,
|
||||
// and requested IP address option must be filled in with the client's IP
|
||||
// address. Also req must contain a valid chaddr according to
|
||||
// [netutil.ValidateMAC]. rw must not be nil, reqIP must be a valid IPv4
|
||||
// address.
|
||||
func (iface *dhcpInterfaceV4) handleInitReboot(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
req *layers.DHCPv4,
|
||||
reqIP netip.Addr,
|
||||
) {
|
||||
l := iface.common.logger
|
||||
|
||||
if !reqIP.Is4() {
|
||||
l.DebugContext(ctx, "bad requested address", "requestedip", reqIP)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ciaddr must be zero. The client is seeking to verify a previously
|
||||
// allocated, cached configuration.
|
||||
ciaddr, _ := netip.AddrFromSlice(req.ClientIP)
|
||||
if ciaddr.IsValid() && !ciaddr.IsUnspecified() {
|
||||
l.DebugContext(ctx, "unexpected ciaddr in init-reboot request", "ciaddr", ciaddr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the lease exists and matches.
|
||||
mac := req.ClientHWAddr
|
||||
mk := macToKey(mac)
|
||||
|
||||
iface.common.indexMu.Lock()
|
||||
defer iface.common.indexMu.Unlock()
|
||||
|
||||
lease, hasLease := iface.common.leases[mk]
|
||||
if !hasLease {
|
||||
// If the DHCP server has no record of this client, then it MUST remain
|
||||
// silent, and MAY output a warning to the network administrator.
|
||||
l.WarnContext(ctx, "no existing lease", "mac", mac)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if lease.IP != reqIP {
|
||||
l.WarnContext(ctx, "init-reboot request mismatched", "requested", reqIP, "lease", lease.IP)
|
||||
iface.respondNAK(ctx, rw, req)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Commit the lease and send ACK.
|
||||
iface.commitLease(ctx, lease, hostname4(req))
|
||||
iface.respondACK(ctx, rw, req, lease)
|
||||
}
|
||||
|
||||
// handleRenew handles messages of type DHCPREQUEST in RENEWING or REBINDING
|
||||
// state. rw must not be nil, ip should be a previously leased address, req
|
||||
// must contain a valid chaddr according to [netutil.ValidateMAC].
|
||||
func (iface *dhcpInterfaceV4) handleRenew(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
req *layers.DHCPv4,
|
||||
ip netip.Addr,
|
||||
) {
|
||||
l := iface.common.logger
|
||||
|
||||
// Check if the lease exists and matches.
|
||||
mac := req.ClientHWAddr
|
||||
mk := macToKey(mac)
|
||||
|
||||
iface.common.indexMu.Lock()
|
||||
defer iface.common.indexMu.Unlock()
|
||||
|
||||
lease, hasLease := iface.common.leases[mk]
|
||||
if !hasLease {
|
||||
// If the DHCP server has no record of this client, then it MUST remain
|
||||
// silent, and MAY output a warning to the network administrator.
|
||||
l.InfoContext(ctx, "no existing lease", "mac", mac)
|
||||
|
||||
// TODO(e.burkov): Investigate if we should respond with NAK.
|
||||
return
|
||||
}
|
||||
|
||||
if lease.IP != ip {
|
||||
l.DebugContext(ctx, "renew request mismatched", "ciaddr", ip, "lease", lease.IP)
|
||||
iface.respondNAK(ctx, rw, req)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Commit the lease and send ACK.
|
||||
iface.commitLease(ctx, lease, hostname4(req))
|
||||
iface.respondACK(ctx, rw, req, lease)
|
||||
}
|
||||
|
||||
// handleDecline handles messages of type DHCPDECLINE. req should contain a
|
||||
// requested IP address, as previously offered by the server. ip should be a
|
||||
// previously leased address, req must contain a valid chaddr according to
|
||||
// [netutil.ValidateMAC].
|
||||
//
|
||||
// TODO(e.burkov): Log the message option, as the request should include one.
|
||||
//
|
||||
// TODO(e.burkov): Consider DRY'ing this with [dhcpInterfaceV4.handleRelease].
|
||||
func (iface *dhcpInterfaceV4) handleDecline(
|
||||
ctx context.Context,
|
||||
ip netip.Addr,
|
||||
req *layers.DHCPv4,
|
||||
) {
|
||||
l := iface.common.logger
|
||||
|
||||
// Check if the lease exists and matches.
|
||||
mac := req.ClientHWAddr
|
||||
mk := macToKey(mac)
|
||||
|
||||
iface.common.indexMu.Lock()
|
||||
defer iface.common.indexMu.Unlock()
|
||||
|
||||
lease, hasLease := iface.common.leases[mk]
|
||||
if !hasLease {
|
||||
l.ErrorContext(ctx, "decline message for non-existing lease", "mac", mac)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if lease.IP != ip {
|
||||
l.ErrorContext(ctx, "decline mismatch", "ip", ip, "lease", lease.IP)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
l.WarnContext(ctx, "lease reported to be unavailable", "ip", lease.IP)
|
||||
|
||||
err := iface.common.index.remove(ctx, l, lease, iface.common)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "removing lease", slogutil.KeyError, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
iface.common.blockLease(lease, iface.clock)
|
||||
}
|
||||
|
||||
// handleRelease handles messages of type DHCPRELEASE. ip should be valid,
|
||||
// previously allocated by the server and correspond to the client's lease. req
|
||||
// must contain a valid chaddr according to [netutil.ValidateMAC].
|
||||
//
|
||||
// TODO(e.burkov): Consider DRY'ing this with [dhcpInterfaceV4.handleDecline].
|
||||
func (iface *dhcpInterfaceV4) handleRelease(
|
||||
ctx context.Context,
|
||||
ip netip.Addr,
|
||||
req *layers.DHCPv4,
|
||||
) {
|
||||
l := iface.common.logger
|
||||
|
||||
// Check if the lease exists and matches.
|
||||
mac := req.ClientHWAddr
|
||||
mk := macToKey(mac)
|
||||
|
||||
iface.common.indexMu.Lock()
|
||||
defer iface.common.indexMu.Unlock()
|
||||
|
||||
lease, hasLease := iface.common.leases[mk]
|
||||
if !hasLease {
|
||||
l.ErrorContext(ctx, "release message for non-existing lease", "mac", mac)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if lease.IP != ip {
|
||||
l.ErrorContext(ctx, "release mismatch", "ip", ip, "lease", lease.IP)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err := iface.common.index.remove(ctx, l, lease, iface.common)
|
||||
if err != nil {
|
||||
l.ErrorContext(ctx, "removing lease", slogutil.KeyError, err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// dhcpInterfacesV4 is a slice of network interfaces of IPv4 address family.
|
||||
type dhcpInterfacesV4 []*dhcpInterfaceV4
|
||||
|
||||
@@ -598,19 +324,6 @@ func (ifaces dhcpInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, ok boo
|
||||
return ifaces[i].common, true
|
||||
}
|
||||
|
||||
// findInterface returns the first DHCPv4 interface within ifaces containing
|
||||
// ip. It returns false if there is no such interface. ip must be valid.
|
||||
func (ifaces dhcpInterfacesV4) findInterface(ip netip.Addr) (iface *dhcpInterfaceV4, ok bool) {
|
||||
i := slices.IndexFunc(ifaces, func(iface *dhcpInterfaceV4) (contains bool) {
|
||||
return iface.subnet.Contains(ip)
|
||||
})
|
||||
if i < 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return ifaces[i], true
|
||||
}
|
||||
|
||||
// allocateLease allocates a new lease for the MAC address. If there are no IP
|
||||
// addresses left, both l and err are nil. mac must be a valid according to
|
||||
// [netutil.ValidateMAC].
|
||||
@@ -677,3 +390,29 @@ func (iface *dhcpInterfaceV4) reserveLease(
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// respond4 sends a DHCPv4 response. fd and resp must not be nil.
|
||||
func respond4(fd *frameData, resp *layers.DHCPv4) (err error) {
|
||||
buf := gopacket.NewSerializeBuffer()
|
||||
opts := gopacket.SerializeOptions{
|
||||
FixLengths: true,
|
||||
ComputeChecksums: true,
|
||||
}
|
||||
|
||||
eth := &layers.Ethernet{
|
||||
SrcMAC: fd.ether.SrcMAC,
|
||||
DstMAC: fd.ether.DstMAC,
|
||||
EthernetType: layers.EthernetTypeIPv4,
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Handle IP layer.
|
||||
|
||||
all := []gopacket.SerializableLayer{eth, resp}
|
||||
|
||||
err = gopacket.SerializeLayers(buf, opts, all...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fd.device.WritePacketData(buf.Bytes())
|
||||
}
|
||||
Reference in new issue
Block a user