AdGuardHome/internal/dhcpsvc/handler4.go
Eugene Burkov d4ee146f6b Pull request #2422: 4923 gopacket dhcp vol.10
Merge in DNS/adguard-home from 4923-gopacket-dhcp-vol.10 to master

Squashed commit of the following:

commit ef2106a35aadecf8340314535ec232f91ff44b50
Merge: f14e68781 f69da062e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Jun 9 16:13:52 2025 +0300

    Merge branch 'master' into 4923-gopacket-dhcp-vol.10

commit f14e687815eb66a7c161dfaa3655c41fded643a8
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Jun 6 18:36:32 2025 +0300

    dhcpsvc: imp code, docs

commit c94b428d6fd277a840476f02820bd065cde17d6c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jun 5 13:21:37 2025 +0300

    dhcpsvc: imp code

commit 57eaea08e572fea41295d373b547f614a0c099ac
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Jun 4 19:48:03 2025 +0300

    dhcpsvc: add methods

commit b32218848cf2a90ecfc9cc6cf12c2579bcb1a2df
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 30 15:57:53 2025 +0300

    dhcpsvc: add handlers

commit 615f1248160351f3bcdb040fb867d32630979d40
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Jan 14 16:10:01 2025 +0300

    dhcpsvc: multiplex dhcpv4
2025-06-10 12:28:51 +03:00

131 lines
3.9 KiB
Go

package dhcpsvc
import (
"context"
"fmt"
"net/netip"
"github.com/AdguardTeam/golibs/errors"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
// serveV4 handles the ethernet packet of IPv4 type. rw and pkt must not be
// nil.
func (srv *DHCPServer) serveV4(
ctx context.Context,
rw responseWriter4,
pkt gopacket.Packet,
) (err error) {
defer func() { err = errors.Annotate(err, "serving dhcpv4: %w") }()
req, ok := pkt.Layer(layers.LayerTypeDHCPv4).(*layers.DHCPv4)
if !ok {
// TODO(e.burkov): Consider adding some debug information about the
// actual received packet.
srv.logger.DebugContext(ctx, "skipping non-dhcpv4 packet")
return nil
}
// TODO(e.burkov): Handle duplicate Xid.
if req.Operation != layers.DHCPOpRequest {
srv.logger.DebugContext(ctx, "skipping non-request dhcpv4 packet", "op", req.Operation)
return nil
}
typ, ok := msg4Type(req)
if !ok {
// The "DHCP message type" option - must be included in every DHCP
// message.
//
// See https://datatracker.ietf.org/doc/html/rfc2131#section-3.
return fmt.Errorf("dhcpv4: message type: %w", errors.ErrNoValue)
}
return srv.handleDHCPv4(ctx, rw, typ, req)
}
// handleDHCPv4 handles the DHCPv4 message of the given type.
func (srv *DHCPServer) handleDHCPv4(
ctx context.Context,
rw responseWriter4,
typ layers.DHCPMsgType,
req *layers.DHCPv4,
) (err error) {
// Each interface should handle the DISCOVER and REQUEST messages offer and
// allocate the available leases. The RELEASE and DECLINE messages should
// be handled by the server itself as it should remove the lease.
switch typ {
case layers.DHCPMsgTypeDiscover:
srv.handleDiscover(ctx, rw, req)
case layers.DHCPMsgTypeRequest:
srv.handleRequest(ctx, rw, req)
case layers.DHCPMsgTypeRelease:
// TODO(e.burkov): Remove the lease, either allocated or offered.
case layers.DHCPMsgTypeDecline:
// TODO(e.burkov): Remove the allocated lease. RFC tells it only
// possible if the client found the address already in use.
default:
// TODO(e.burkov): Handle DHCPINFORM.
return fmt.Errorf("dhcpv4: request type: %w: %v", errors.ErrBadEnumValue, typ)
}
return nil
}
// handleDiscover handles the DHCPv4 message of discover type.
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 request type.
func (srv *DHCPServer) handleRequest(ctx context.Context, rw responseWriter4, req *layers.DHCPv4) {
srvID, hasSrvID := serverID4(req)
reqIP, hasReqIP := requestedIPv4(req)
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, hasIface := srv.interfaces4.findInterface(srvID)
if !hasIface {
srv.logger.DebugContext(ctx, "skipping selecting request", "serverid", srvID)
return
}
iface.handleSelecting(ctx, rw, req, reqIP)
case hasReqIP && !reqIP.IsUnspecified():
// Requested IP address option MUST be filled in with client's notion of
// its previously assigned address.
iface, hasIface := srv.interfaces4.findInterface(reqIP)
if !hasIface {
srv.logger.DebugContext(ctx, "skipping init-reboot request", "requestedip", reqIP)
return
}
iface.handleInitReboot(ctx, rw, req, 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, hasIface := srv.interfaces4.findInterface(ip)
if !hasIface {
srv.logger.DebugContext(ctx, "skipping init-reboot request", "clientip", ip)
return
}
iface.handleRenew(ctx, rw, req)
}
}