dhcpsvc: add handlers
This commit is contained in:
127
internal/dhcpsvc/handler4.go
Normal file
127
internal/dhcpsvc/handler4.go
Normal file
@@ -0,0 +1,127 @@
|
||||
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.
|
||||
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 {
|
||||
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")
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
57
internal/dhcpsvc/handler6.go
Normal file
57
internal/dhcpsvc/handler6.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
// serveV6 handles the ethernet packet of IPv6 type.
|
||||
func (srv *DHCPServer) serveV6(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
pkt gopacket.Packet,
|
||||
) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "serving dhcpv6: %w") }()
|
||||
|
||||
msg, ok := pkt.Layer(layers.LayerTypeDHCPv6).(*layers.DHCPv6)
|
||||
if !ok {
|
||||
srv.logger.DebugContext(ctx, "skipping non-dhcpv6 packet")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Handle duplicate TransactionID.
|
||||
|
||||
typ := msg.MsgType
|
||||
|
||||
return srv.handleDHCPv6(ctx, rw, typ, msg)
|
||||
}
|
||||
|
||||
// handleDHCPv6 handles the DHCPv6 message of the given type.
|
||||
func (srv *DHCPServer) handleDHCPv6(
|
||||
_ context.Context,
|
||||
_ responseWriter4,
|
||||
typ layers.DHCPv6MsgType,
|
||||
_ *layers.DHCPv6,
|
||||
) (err error) {
|
||||
switch typ {
|
||||
case
|
||||
layers.DHCPv6MsgTypeSolicit,
|
||||
layers.DHCPv6MsgTypeRequest,
|
||||
layers.DHCPv6MsgTypeConfirm,
|
||||
layers.DHCPv6MsgTypeRenew,
|
||||
layers.DHCPv6MsgTypeRebind,
|
||||
layers.DHCPv6MsgTypeInformationRequest,
|
||||
layers.DHCPv6MsgTypeRelease,
|
||||
layers.DHCPv6MsgTypeDecline:
|
||||
// TODO(e.burkov): Handle messages.
|
||||
default:
|
||||
return fmt.Errorf("dhcpv6: request type: %w: %v", errors.ErrBadEnumValue, typ)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2,15 +2,20 @@ package dhcpsvc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"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)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
defer slogutil.RecoverAndLog(ctx, srv.logger)
|
||||
|
||||
@@ -23,11 +28,15 @@ func (srv *DHCPServer) serve(ctx context.Context) {
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// TODO(e.burkov): Set the response writer.
|
||||
var rw responseWriter4
|
||||
|
||||
switch typ := etherLayer.EthernetType; typ {
|
||||
case layers.EthernetTypeIPv4:
|
||||
err = srv.serveV4(ctx, pkt)
|
||||
err = srv.serveV4(ctx, rw, pkt)
|
||||
case layers.EthernetTypeIPv6:
|
||||
// TODO(e.burkov): Handle DHCPv6 as well.
|
||||
err = srv.serveV6(ctx, rw, pkt)
|
||||
default:
|
||||
srv.logger.DebugContext(ctx, "skipping ethernet packet", "type", typ)
|
||||
|
||||
@@ -39,64 +48,3 @@ func (srv *DHCPServer) serve(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// serveV4 handles the ethernet packet of IPv4 type.
|
||||
func (srv *DHCPServer) serveV4(ctx context.Context, pkt gopacket.Packet) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "serving dhcpv4: %w") }()
|
||||
|
||||
msg, ok := pkt.Layer(layers.LayerTypeDHCPv4).(*layers.DHCPv4)
|
||||
if !ok {
|
||||
srv.logger.DebugContext(ctx, "skipping non-dhcpv4 packet")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Handle duplicate Xid.
|
||||
|
||||
typ, ok := msgType(msg)
|
||||
if !ok {
|
||||
return errors.Error("no message type in the dhcpv4 message")
|
||||
}
|
||||
|
||||
return srv.handleDHCPv4(ctx, typ, msg)
|
||||
}
|
||||
|
||||
// handleDHCPv4 handles the DHCPv4 message of the given type.
|
||||
func (srv *DHCPServer) handleDHCPv4(
|
||||
ctx context.Context,
|
||||
typ layers.DHCPMsgType,
|
||||
msg *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:
|
||||
for _, iface := range srv.interfaces4 {
|
||||
go iface.handleDiscover(ctx, msg)
|
||||
}
|
||||
case layers.DHCPMsgTypeRequest:
|
||||
for _, iface := range srv.interfaces4 {
|
||||
go iface.handleRequest(ctx, msg)
|
||||
}
|
||||
case layers.DHCPMsgTypeRelease:
|
||||
addr, ok := netip.AddrFromSlice(msg.ClientIP)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid client ip in the release message")
|
||||
}
|
||||
|
||||
return srv.removeLeaseByAddr(ctx, addr)
|
||||
case layers.DHCPMsgTypeDecline:
|
||||
addr, ok := requestedIP(msg)
|
||||
if !ok {
|
||||
return fmt.Errorf("no requested ip in the decline message")
|
||||
}
|
||||
|
||||
return srv.removeLeaseByAddr(ctx, addr)
|
||||
default:
|
||||
// TODO(e.burkov): Handle DHCPINFORM.
|
||||
return fmt.Errorf("dhcpv4 message type: %w: %v", errors.ErrBadEnumValue, typ)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ type DHCPServer struct {
|
||||
// logger logs common DHCP events.
|
||||
logger *slog.Logger
|
||||
|
||||
// TODO(e.burkov): !! implement and set
|
||||
// TODO(e.burkov): Implement and set.
|
||||
packetSource gopacket.PacketSource
|
||||
|
||||
// localTLD is the top-level domain name to use for resolving DHCP clients'
|
||||
@@ -156,7 +156,7 @@ func newInterfaces(
|
||||
func (srv *DHCPServer) Start(ctx context.Context) (err error) {
|
||||
srv.logger.DebugContext(ctx, "starting dhcp server")
|
||||
|
||||
// TODO(e.burkov): !! listen to configured interfaces
|
||||
// TODO(e.burkov): Listen to configured interfaces.
|
||||
|
||||
go srv.serve(context.Background())
|
||||
|
||||
@@ -166,7 +166,7 @@ func (srv *DHCPServer) Start(ctx context.Context) (err error) {
|
||||
func (srv *DHCPServer) Shutdown(ctx context.Context) (err error) {
|
||||
srv.logger.DebugContext(ctx, "shutting down dhcp server")
|
||||
|
||||
// TODO(e.burkov): !! close the packet source
|
||||
// TODO(e.burkov): Close the packet source.
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -372,6 +372,8 @@ func (srv *DHCPServer) RemoveLease(ctx context.Context, l *Lease) (err error) {
|
||||
|
||||
// removeLeaseByAddr removes the lease with the given IP address from the
|
||||
// server. It returns an error if the lease can't be removed.
|
||||
//
|
||||
// TODO(e.burkov): !! Use.
|
||||
func (srv *DHCPServer) removeLeaseByAddr(ctx context.Context, addr netip.Addr) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "removing lease by address: %w") }()
|
||||
|
||||
|
||||
@@ -155,22 +155,6 @@ func newDHCPInterfaceV4(
|
||||
return iface, nil
|
||||
}
|
||||
|
||||
// dhcpInterfacesV4 is a slice of network interfaces of IPv4 address family.
|
||||
type dhcpInterfacesV4 []*dhcpInterfaceV4
|
||||
|
||||
// find returns the first network interface within ifaces containing ip. It
|
||||
// returns false if there is no such interface.
|
||||
func (ifaces dhcpInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, 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].common, true
|
||||
}
|
||||
|
||||
// options returns the implicit and explicit options for the interface. The two
|
||||
// lists are disjoint and the implicit options are initialized with default
|
||||
// values.
|
||||
@@ -362,8 +346,8 @@ func compareV4OptionCodes(a, b layers.DHCPOption) (res int) {
|
||||
return int(a.Type) - int(b.Type)
|
||||
}
|
||||
|
||||
// msgType returns the message type of msg, if it's present within the options.
|
||||
func msgType(msg *layers.DHCPv4) (typ layers.DHCPMsgType, ok bool) {
|
||||
// msg4Type returns the message type of msg, if it's present within the options.
|
||||
func msg4Type(msg *layers.DHCPv4) (typ layers.DHCPMsgType, ok bool) {
|
||||
for _, opt := range msg.Options {
|
||||
if opt.Type == layers.DHCPOptMessageType && len(opt.Data) > 0 {
|
||||
return layers.DHCPMsgType(opt.Data[0]), true
|
||||
@@ -373,7 +357,9 @@ func msgType(msg *layers.DHCPv4) (typ layers.DHCPMsgType, ok bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func requestedIP(msg *layers.DHCPv4) (ip netip.Addr, ok bool) {
|
||||
// requestedIPv4 returns the IPv4 address, requested by client in the DHCP
|
||||
// message, if any.
|
||||
func requestedIPv4(msg *layers.DHCPv4) (ip netip.Addr, ok bool) {
|
||||
for _, opt := range msg.Options {
|
||||
if opt.Type == layers.DHCPOptRequestIP && len(opt.Data) == net.IPv4len {
|
||||
return netip.AddrFromSlice(opt.Data)
|
||||
@@ -383,10 +369,80 @@ func requestedIP(msg *layers.DHCPv4) (ip netip.Addr, ok bool) {
|
||||
return netip.Addr{}, false
|
||||
}
|
||||
|
||||
func (iface *dhcpInterfaceV4) handleDiscover(ctx context.Context, msg *layers.DHCPv4) {
|
||||
// TODO(e.burkov): Implement.
|
||||
// serverID4 returns the server ID of the DHCP message, if any.
|
||||
func serverID4(msg *layers.DHCPv4) (ip netip.Addr, ok bool) {
|
||||
for _, opt := range msg.Options {
|
||||
if opt.Type == layers.DHCPOptServerID && len(opt.Data) == net.IPv4len {
|
||||
return netip.AddrFromSlice(opt.Data)
|
||||
}
|
||||
}
|
||||
|
||||
return netip.Addr{}, false
|
||||
}
|
||||
|
||||
func (iface *dhcpInterfaceV4) handleRequest(ctx context.Context, msg *layers.DHCPv4) {
|
||||
// TODO(e.burkov): Implement.
|
||||
// handleDiscover handles messages of type discover.
|
||||
func (iface *dhcpInterfaceV4) handleDiscover(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
msg *layers.DHCPv4,
|
||||
) {
|
||||
// TODO(e.burkov): !! Implement.
|
||||
}
|
||||
|
||||
// handleSelecting handles messages of type request in SELECTING state.
|
||||
func (iface *dhcpInterfaceV4) handleSelecting(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
msg *layers.DHCPv4,
|
||||
reqIP netip.Addr,
|
||||
) {
|
||||
// TODO(e.burkov): !! Implement.
|
||||
}
|
||||
|
||||
// handleSelecting handles messages of type request in INIT-REBOOT state.
|
||||
func (iface *dhcpInterfaceV4) handleInitReboot(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
msg *layers.DHCPv4,
|
||||
reqIP netip.Addr,
|
||||
) {
|
||||
// TODO(e.burkov): !! Implement.
|
||||
}
|
||||
|
||||
// handleRenew handles messages of type request in RENEWING or REBINDING state.
|
||||
func (iface *dhcpInterfaceV4) handleRenew(
|
||||
ctx context.Context,
|
||||
rw responseWriter4,
|
||||
req *layers.DHCPv4,
|
||||
) {
|
||||
// TODO(e.burkov): !! Implement.
|
||||
}
|
||||
|
||||
// dhcpInterfacesV4 is a slice of network interfaces of IPv4 address family.
|
||||
type dhcpInterfacesV4 []*dhcpInterfaceV4
|
||||
|
||||
// find returns the first network interface within ifaces containing ip. It
|
||||
// returns false if there is no such interface.
|
||||
func (ifaces dhcpInterfacesV4) find(ip netip.Addr) (iface4 *netInterface, 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].common, true
|
||||
}
|
||||
|
||||
// findInterface returns the first DHCPv4 interface within ifaces containing
|
||||
// ip. It returns false if there is no such interface.
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user