all: sync with master
This commit is contained in:
@@ -83,7 +83,7 @@ func (s *v4Server) newDHCPConn(iface *net.Interface) (c net.PacketConn, err erro
|
||||
|
||||
// wrapErrs is a helper to wrap the errors from two independent underlying
|
||||
// connections.
|
||||
func (c *dhcpConn) wrapErrs(action string, udpConnErr, rawConnErr error) (err error) {
|
||||
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)
|
||||
@@ -129,7 +129,7 @@ func (c *dhcpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
// connection.
|
||||
return c.udpConn.WriteTo(p, addr)
|
||||
default:
|
||||
return 0, fmt.Errorf("peer is of unexpected type %T", addr)
|
||||
return 0, fmt.Errorf("addr has an unexpected type %T", addr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,32 +188,20 @@ func (c *dhcpConn) SetWriteDeadline(t time.Time) error {
|
||||
)
|
||||
}
|
||||
|
||||
// ipv4DefaultTTL is the default Time to Live value as recommended by
|
||||
// RFC-1700 (https://datatracker.ietf.org/doc/html/rfc1700) in seconds.
|
||||
// ipv4DefaultTTL is the default Time to Live value in seconds as recommended by
|
||||
// RFC-1700.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1700.
|
||||
const ipv4DefaultTTL = 64
|
||||
|
||||
// errInvalidPktDHCP is returned when the provided payload is not a valid DHCP
|
||||
// packet.
|
||||
const errInvalidPktDHCP errors.Error = "packet is not a valid dhcp packet"
|
||||
|
||||
// buildEtherPkt wraps the payload with IPv4, UDP and Ethernet frames. The
|
||||
// payload is expected to be an encoded DHCP packet.
|
||||
// buildEtherPkt wraps the payload with IPv4, UDP and Ethernet frames.
|
||||
// Validation of the payload is a caller's responsibility.
|
||||
func (c *dhcpConn) buildEtherPkt(payload []byte, peer *dhcpUnicastAddr) (pkt []byte, err error) {
|
||||
dhcpLayer := gopacket.NewPacket(payload, layers.LayerTypeDHCPv4, gopacket.DecodeOptions{
|
||||
NoCopy: true,
|
||||
}).Layer(layers.LayerTypeDHCPv4)
|
||||
|
||||
// Check if the decoding succeeded and the resulting layer doesn't
|
||||
// contain any errors. It should guarantee panic-safe converting of the
|
||||
// layer into gopacket.SerializableLayer.
|
||||
if dhcpLayer == nil || dhcpLayer.LayerType() != layers.LayerTypeDHCPv4 {
|
||||
return nil, errInvalidPktDHCP
|
||||
}
|
||||
|
||||
udpLayer := &layers.UDP{
|
||||
SrcPort: dhcpv4.ServerPort,
|
||||
DstPort: dhcpv4.ClientPort,
|
||||
}
|
||||
|
||||
ipv4Layer := &layers.IPv4{
|
||||
Version: uint8(layers.IPProtocolIPv4),
|
||||
Flags: layers.IPv4DontFragment,
|
||||
@@ -226,6 +214,7 @@ func (c *dhcpConn) buildEtherPkt(payload []byte, peer *dhcpUnicastAddr) (pkt []b
|
||||
// Ignore the error since it's only returned for invalid network layer's
|
||||
// type.
|
||||
_ = udpLayer.SetNetworkLayerForChecksum(ipv4Layer)
|
||||
|
||||
ethLayer := &layers.Ethernet{
|
||||
SrcMAC: c.srcMAC,
|
||||
DstMAC: peer.HardwareAddr,
|
||||
@@ -233,10 +222,19 @@ func (c *dhcpConn) buildEtherPkt(payload []byte, peer *dhcpUnicastAddr) (pkt []b
|
||||
}
|
||||
|
||||
buf := gopacket.NewSerializeBuffer()
|
||||
err = gopacket.SerializeLayers(buf, gopacket.SerializeOptions{
|
||||
setts := gopacket.SerializeOptions{
|
||||
FixLengths: true,
|
||||
ComputeChecksums: true,
|
||||
}, ethLayer, ipv4Layer, udpLayer, dhcpLayer.(gopacket.SerializableLayer))
|
||||
}
|
||||
|
||||
err = gopacket.SerializeLayers(
|
||||
buf,
|
||||
setts,
|
||||
ethLayer,
|
||||
ipv4Layer,
|
||||
udpLayer,
|
||||
gopacket.Payload(payload),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("serializing layers: %w", err)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestDHCPConn_WriteTo_common(t *testing.T) {
|
||||
n, err := conn.WriteTo(nil, &unexpectedAddrType{})
|
||||
require.Error(t, err)
|
||||
|
||||
testutil.AssertErrorMsg(t, "peer is of unexpected type *dhcpd.unexpectedAddrType", err)
|
||||
testutil.AssertErrorMsg(t, "addr has an unexpected type *dhcpd.unexpectedAddrType", err)
|
||||
assert.Zero(t, n)
|
||||
})
|
||||
}
|
||||
@@ -91,14 +91,13 @@ func TestBuildEtherPkt(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("non-serializable", func(t *testing.T) {
|
||||
t.Run("bad_payload", func(t *testing.T) {
|
||||
// Create an invalid DHCP packet.
|
||||
invalidPayload := []byte{1, 2, 3, 4}
|
||||
pkt, err := conn.buildEtherPkt(invalidPayload, nil)
|
||||
require.Error(t, err)
|
||||
pkt, err := conn.buildEtherPkt(invalidPayload, peer)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.ErrorIs(t, err, errInvalidPktDHCP)
|
||||
assert.Empty(t, pkt)
|
||||
assert.NotEmpty(t, pkt)
|
||||
})
|
||||
|
||||
t.Run("serializing_error", func(t *testing.T) {
|
||||
|
||||
@@ -9,26 +9,31 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
)
|
||||
|
||||
// The aliases for DHCP option types available for explicit declaration.
|
||||
//
|
||||
// TODO(e.burkov): Add an option for classless routes.
|
||||
const (
|
||||
hexTyp = "hex"
|
||||
ipTyp = "ip"
|
||||
ipsTyp = "ips"
|
||||
textTyp = "text"
|
||||
typDel = "del"
|
||||
typBool = "bool"
|
||||
typDur = "dur"
|
||||
typHex = "hex"
|
||||
typIP = "ip"
|
||||
typIPs = "ips"
|
||||
typText = "text"
|
||||
typU8 = "u8"
|
||||
typU16 = "u16"
|
||||
)
|
||||
|
||||
// parseDHCPOptionHex parses a DHCP option as a hex-encoded string. For
|
||||
// example:
|
||||
//
|
||||
// 252 hex 736f636b733a2f2f70726f78792e6578616d706c652e6f7267
|
||||
//
|
||||
// parseDHCPOptionHex parses a DHCP option as a hex-encoded string.
|
||||
func parseDHCPOptionHex(s string) (val dhcpv4.OptionValue, err error) {
|
||||
var data []byte
|
||||
data, err = hex.DecodeString(s)
|
||||
@@ -39,15 +44,12 @@ func parseDHCPOptionHex(s string) (val dhcpv4.OptionValue, err error) {
|
||||
return dhcpv4.OptionGeneric{Data: data}, nil
|
||||
}
|
||||
|
||||
// parseDHCPOptionIP parses a DHCP option as a single IP address. For example:
|
||||
//
|
||||
// 6 ip 192.168.1.1
|
||||
//
|
||||
// parseDHCPOptionIP parses a DHCP option as a single IP address.
|
||||
func parseDHCPOptionIP(s string) (val dhcpv4.OptionValue, err error) {
|
||||
var ip net.IP
|
||||
// All DHCPv4 options require IPv4, so don't put the 16-byte version.
|
||||
// Otherwise, the clients will receive weird data that looks like four
|
||||
// IPv4 addresses.
|
||||
// Otherwise, the clients will receive weird data that looks like four IPv4
|
||||
// addresses.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2688.
|
||||
if ip, err = netutil.ParseIPv4(s); err != nil {
|
||||
@@ -58,133 +60,348 @@ func parseDHCPOptionIP(s string) (val dhcpv4.OptionValue, err error) {
|
||||
}
|
||||
|
||||
// parseDHCPOptionIPs parses a DHCP option as a comma-separates list of IP
|
||||
// addresses. For example:
|
||||
//
|
||||
// 6 ips 192.168.1.1,192.168.1.2
|
||||
//
|
||||
// addresses.
|
||||
func parseDHCPOptionIPs(s string) (val dhcpv4.OptionValue, err error) {
|
||||
var ips dhcpv4.IPs
|
||||
var ip net.IP
|
||||
var ip dhcpv4.OptionValue
|
||||
for i, ipStr := range strings.Split(s, ",") {
|
||||
// See notes in the ipDHCPOptionParserHandler.
|
||||
if ip, err = netutil.ParseIPv4(ipStr); err != nil {
|
||||
ip, err = parseDHCPOptionIP(ipStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing ip at index %d: %w", i, err)
|
||||
}
|
||||
|
||||
ips = append(ips, ip)
|
||||
ips = append(ips, net.IP(ip.(dhcpv4.IP)))
|
||||
}
|
||||
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
// parseDHCPOptionText parses a DHCP option as a simple UTF-8 encoded
|
||||
// text. For example:
|
||||
//
|
||||
// 252 text http://192.168.1.1/wpad.dat
|
||||
//
|
||||
func parseDHCPOptionText(s string) (val dhcpv4.OptionValue) {
|
||||
return dhcpv4.OptionGeneric{Data: []byte(s)}
|
||||
// parseDHCPOptionDur parses a DHCP option as a duration in a human-readable
|
||||
// form.
|
||||
func parseDHCPOptionDur(s string) (val dhcpv4.OptionValue, err error) {
|
||||
var v timeutil.Duration
|
||||
err = v.UnmarshalText([]byte(s))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding dur: %w", err)
|
||||
}
|
||||
|
||||
return dhcpv4.Duration(v.Duration), nil
|
||||
}
|
||||
|
||||
// parseDHCPOption parses an option. See the documentation of parseDHCPOption*
|
||||
// for more info.
|
||||
func parseDHCPOption(s string) (opt dhcpv4.Option, err error) {
|
||||
// parseDHCPOptionUint parses a DHCP option as an unsigned integer. bitSize is
|
||||
// expected to be 8 or 16.
|
||||
func parseDHCPOptionUint(s string, bitSize int) (val dhcpv4.OptionValue, err error) {
|
||||
var v uint64
|
||||
v, err = strconv.ParseUint(s, 10, bitSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding u%d: %w", bitSize, err)
|
||||
}
|
||||
|
||||
switch bitSize {
|
||||
case 8:
|
||||
return dhcpv4.OptionGeneric{Data: []byte{uint8(v)}}, nil
|
||||
case 16:
|
||||
return dhcpv4.Uint16(v), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported size of integer %d", bitSize)
|
||||
}
|
||||
}
|
||||
|
||||
// parseDHCPOptionBool parses a DHCP option as a boolean value. See
|
||||
// [strconv.ParseBool] for available values.
|
||||
func parseDHCPOptionBool(s string) (val dhcpv4.OptionValue, err error) {
|
||||
var v bool
|
||||
v, err = strconv.ParseBool(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding bool: %w", err)
|
||||
}
|
||||
|
||||
rawVal := [1]byte{}
|
||||
if v {
|
||||
rawVal[0] = 1
|
||||
}
|
||||
|
||||
return dhcpv4.OptionGeneric{Data: rawVal[:]}, nil
|
||||
}
|
||||
|
||||
// parseDHCPOptionVal parses a DHCP option value considering typ.
|
||||
func parseDHCPOptionVal(typ, valStr string) (val dhcpv4.OptionValue, err error) {
|
||||
switch typ {
|
||||
case typBool:
|
||||
val, err = parseDHCPOptionBool(valStr)
|
||||
case typDel:
|
||||
val = dhcpv4.OptionGeneric{Data: nil}
|
||||
case typDur:
|
||||
val, err = parseDHCPOptionDur(valStr)
|
||||
case typHex:
|
||||
val, err = parseDHCPOptionHex(valStr)
|
||||
case typIP:
|
||||
val, err = parseDHCPOptionIP(valStr)
|
||||
case typIPs:
|
||||
val, err = parseDHCPOptionIPs(valStr)
|
||||
case typText:
|
||||
val = dhcpv4.String(valStr)
|
||||
case typU8:
|
||||
val, err = parseDHCPOptionUint(valStr, 8)
|
||||
case typU16:
|
||||
val, err = parseDHCPOptionUint(valStr, 16)
|
||||
default:
|
||||
err = fmt.Errorf("unknown option type %q", typ)
|
||||
}
|
||||
|
||||
return val, err
|
||||
}
|
||||
|
||||
// parseDHCPOption parses an option. For the del option value is ignored. The
|
||||
// examples of possible option strings:
|
||||
//
|
||||
// - 1 bool true
|
||||
// - 2 del
|
||||
// - 3 dur 2h5s
|
||||
// - 4 hex 736f636b733a2f2f70726f78792e6578616d706c652e6f7267
|
||||
// - 5 ip 192.168.1.1
|
||||
// - 6 ips 192.168.1.1,192.168.1.2
|
||||
// - 7 text http://192.168.1.1/wpad.dat
|
||||
// - 8 u8 255
|
||||
// - 9 u16 65535
|
||||
func parseDHCPOption(s string) (code dhcpv4.OptionCode, val dhcpv4.OptionValue, err error) {
|
||||
defer func() { err = errors.Annotate(err, "invalid option string %q: %w", s) }()
|
||||
|
||||
s = strings.TrimSpace(s)
|
||||
parts := strings.SplitN(s, " ", 3)
|
||||
if len(parts) < 3 {
|
||||
return opt, errors.Error("need at least three fields")
|
||||
|
||||
var valStr string
|
||||
if pl := len(parts); pl < 3 {
|
||||
if pl < 2 || parts[1] != typDel {
|
||||
return nil, nil, errors.Error("bad option format")
|
||||
}
|
||||
} else {
|
||||
valStr = parts[2]
|
||||
}
|
||||
|
||||
var code64 uint64
|
||||
code64, err = strconv.ParseUint(parts[0], 10, 8)
|
||||
if err != nil {
|
||||
return opt, fmt.Errorf("parsing option code: %w", err)
|
||||
}
|
||||
|
||||
var optVal dhcpv4.OptionValue
|
||||
switch typ, val := parts[1], parts[2]; typ {
|
||||
case hexTyp:
|
||||
optVal, err = parseDHCPOptionHex(val)
|
||||
case ipTyp:
|
||||
optVal, err = parseDHCPOptionIP(val)
|
||||
case ipsTyp:
|
||||
optVal, err = parseDHCPOptionIPs(val)
|
||||
case textTyp:
|
||||
optVal = parseDHCPOptionText(val)
|
||||
default:
|
||||
return opt, fmt.Errorf("unknown option type %q", typ)
|
||||
return nil, nil, fmt.Errorf("parsing option code: %w", err)
|
||||
}
|
||||
|
||||
val, err = parseDHCPOptionVal(parts[1], valStr)
|
||||
if err != nil {
|
||||
return opt, err
|
||||
// Don't wrap an error since it's informative enough as is and there
|
||||
// also the deferred annotation.
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return dhcpv4.Option{
|
||||
Code: dhcpv4.GenericOptionCode(code64),
|
||||
Value: optVal,
|
||||
}, nil
|
||||
return dhcpv4.GenericOptionCode(code64), val, nil
|
||||
}
|
||||
|
||||
// prepareOptions builds the set of DHCP options according to host requirements
|
||||
// document and values from conf.
|
||||
func prepareOptions(conf V4ServerConf) (opts dhcpv4.Options) {
|
||||
// Set default values for host configuration parameters listed in Appendix
|
||||
// A of RFC-2131. Those parameters, if requested by client, should be
|
||||
// returned with values defined by Host Requirements Document.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc2131#appendix-A.
|
||||
//
|
||||
// See also https://datatracker.ietf.org/doc/html/rfc1122,
|
||||
// https://datatracker.ietf.org/doc/html/rfc1123, and
|
||||
// https://datatracker.ietf.org/doc/html/rfc2132.
|
||||
opts = dhcpv4.Options{
|
||||
func prepareOptions(conf V4ServerConf) (implicit, explicit dhcpv4.Options) {
|
||||
// Set default values of host configuration parameters listed in Appendix A
|
||||
// of RFC-2131.
|
||||
implicit = dhcpv4.OptionsFromList(
|
||||
// IP-Layer Per Host
|
||||
dhcpv4.OptionNonLocalSourceRouting.Code(): []byte{0},
|
||||
|
||||
// Set the current recommended default time to live for the
|
||||
// Internet Protocol which is 64, see
|
||||
// https://datatracker.ietf.org/doc/html/rfc1700.
|
||||
dhcpv4.OptionDefaultIPTTL.Code(): []byte{64},
|
||||
// An Internet host that includes embedded gateway code MUST have a
|
||||
// configuration switch to disable the gateway function, and this switch
|
||||
// MUST default to the non-gateway mode.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.5.
|
||||
dhcpv4.OptGeneric(dhcpv4.OptionIPForwarding, []byte{0x0}),
|
||||
|
||||
// A host that supports non-local source-routing MUST have a
|
||||
// configurable switch to disable forwarding, and this switch MUST
|
||||
// default to disabled.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.5.
|
||||
dhcpv4.OptGeneric(dhcpv4.OptionNonLocalSourceRouting, []byte{0x0}),
|
||||
|
||||
// Do not set the Policy Filter Option since it only makes sense when
|
||||
// the non-local source routing is enabled.
|
||||
|
||||
// The minimum legal value is 576.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc2132#section-4.4.
|
||||
dhcpv4.Option{
|
||||
Code: dhcpv4.OptionMaximumDatagramAssemblySize,
|
||||
Value: dhcpv4.Uint16(576),
|
||||
},
|
||||
|
||||
// Set the current recommended default time to live for the Internet
|
||||
// Protocol which is 64.
|
||||
//
|
||||
// See https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml#ip-parameters-2.
|
||||
dhcpv4.OptGeneric(dhcpv4.OptionDefaultIPTTL, []byte{0x40}),
|
||||
|
||||
// For example, after the PTMU estimate is decreased, the timeout should
|
||||
// be set to 10 minutes; once this timer expires and a larger MTU is
|
||||
// attempted, the timeout can be set to a much smaller value.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1191#section-6.6.
|
||||
dhcpv4.Option{
|
||||
Code: dhcpv4.OptionPathMTUAgingTimeout,
|
||||
Value: dhcpv4.Duration(10 * time.Minute),
|
||||
},
|
||||
|
||||
// There is a table describing the MTU values representing all major
|
||||
// data-link technologies in use in the Internet so that each set of
|
||||
// similar MTUs is associated with a plateau value equal to the lowest
|
||||
// MTU in the group.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1191#section-7.
|
||||
dhcpv4.OptGeneric(dhcpv4.OptionPathMTUPlateauTable, []byte{
|
||||
0x0, 0x44,
|
||||
0x1, 0x28,
|
||||
0x1, 0xFC,
|
||||
0x3, 0xEE,
|
||||
0x5, 0xD4,
|
||||
0x7, 0xD2,
|
||||
0x11, 0x0,
|
||||
0x1F, 0xE6,
|
||||
0x45, 0xFA,
|
||||
}),
|
||||
|
||||
// IP-Layer Per Interface
|
||||
|
||||
dhcpv4.OptionPerformMaskDiscovery.Code(): []byte{0},
|
||||
dhcpv4.OptionMaskSupplier.Code(): []byte{0},
|
||||
dhcpv4.OptionPerformRouterDiscovery.Code(): []byte{1},
|
||||
// The all-routers address is preferred wherever possible, see
|
||||
// https://datatracker.ietf.org/doc/html/rfc1256#section-5.1.
|
||||
dhcpv4.OptionRouterSolicitationAddress.Code(): netutil.IPv4allrouter(),
|
||||
dhcpv4.OptionBroadcastAddress.Code(): netutil.IPv4bcast(),
|
||||
// Since nearly all networks in the Internet currently support an MTU of
|
||||
// 576 or greater, we strongly recommend the use of 576 for datagrams
|
||||
// sent to non-local networks.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.3.
|
||||
dhcpv4.Option{
|
||||
Code: dhcpv4.OptionInterfaceMTU,
|
||||
Value: dhcpv4.Uint16(576),
|
||||
},
|
||||
|
||||
// Set the All Subnets Are Local Option to false since commonly the
|
||||
// connected hosts aren't expected to be multihomed.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.3.
|
||||
dhcpv4.OptGeneric(dhcpv4.OptionAllSubnetsAreLocal, []byte{0x00}),
|
||||
|
||||
// Set the Perform Mask Discovery Option to false to provide the subnet
|
||||
// mask by options only.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-3.2.2.9.
|
||||
dhcpv4.OptGeneric(dhcpv4.OptionPerformMaskDiscovery, []byte{0x00}),
|
||||
|
||||
// A system MUST NOT send an Address Mask Reply unless it is an
|
||||
// authoritative agent for address masks. An authoritative agent may be
|
||||
// a host or a gateway, but it MUST be explicitly configured as a
|
||||
// address mask agent.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-3.2.2.9.
|
||||
dhcpv4.OptGeneric(dhcpv4.OptionMaskSupplier, []byte{0x00}),
|
||||
|
||||
// Set the Perform Router Discovery Option to true as per Router
|
||||
// Discovery Document.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1256#section-5.1.
|
||||
dhcpv4.OptGeneric(dhcpv4.OptionPerformRouterDiscovery, []byte{0x01}),
|
||||
|
||||
// The all-routers address is preferred wherever possible.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1256#section-5.1.
|
||||
dhcpv4.Option{
|
||||
Code: dhcpv4.OptionRouterSolicitationAddress,
|
||||
Value: dhcpv4.IP(netutil.IPv4allrouter()),
|
||||
},
|
||||
|
||||
// Don't set the Static Routes Option since it should be set up by
|
||||
// system administrator.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.1.2.
|
||||
|
||||
// A datagram with the destination address of limited broadcast will be
|
||||
// received by every host on the connected physical network but will not
|
||||
// be forwarded outside that network.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-3.2.1.3.
|
||||
dhcpv4.OptBroadcastAddress(netutil.IPv4bcast()),
|
||||
|
||||
// Link-Layer Per Interface
|
||||
|
||||
dhcpv4.OptionTrailerEncapsulation.Code(): []byte{0},
|
||||
dhcpv4.OptionEthernetEncapsulation.Code(): []byte{0},
|
||||
// If the system does not dynamically negotiate use of the trailer
|
||||
// protocol on a per-destination basis, the default configuration MUST
|
||||
// disable the protocol.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-2.3.1.
|
||||
dhcpv4.OptGeneric(dhcpv4.OptionTrailerEncapsulation, []byte{0x00}),
|
||||
|
||||
// For proxy ARP situations, the timeout needs to be on the order of a
|
||||
// minute.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-2.3.2.1.
|
||||
dhcpv4.Option{
|
||||
Code: dhcpv4.OptionArpCacheTimeout,
|
||||
Value: dhcpv4.Duration(time.Minute),
|
||||
},
|
||||
|
||||
// An Internet host that implements sending both the RFC-894 and the
|
||||
// RFC-1042 encapsulations MUST provide a configuration switch to select
|
||||
// which is sent, and this switch MUST default to RFC-894.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-2.3.3.
|
||||
dhcpv4.OptGeneric(dhcpv4.OptionEthernetEncapsulation, []byte{0x00}),
|
||||
|
||||
// TCP Per Host
|
||||
|
||||
dhcpv4.OptionTCPKeepaliveInterval.Code(): dhcpv4.Duration(0).ToBytes(),
|
||||
dhcpv4.OptionTCPKeepaliveGarbage.Code(): []byte{0},
|
||||
// A fixed value must be at least big enough for the Internet diameter,
|
||||
// i.e., the longest possible path. A reasonable value is about twice
|
||||
// the diameter, to allow for continued Internet growth.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-3.2.1.7.
|
||||
dhcpv4.Option{
|
||||
Code: dhcpv4.OptionDefaulTCPTTL,
|
||||
Value: dhcpv4.Duration(60 * time.Second),
|
||||
},
|
||||
|
||||
// The interval MUST be configurable and MUST default to no less than
|
||||
// two hours.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-4.2.3.6.
|
||||
dhcpv4.Option{
|
||||
Code: dhcpv4.OptionTCPKeepaliveInterval,
|
||||
Value: dhcpv4.Duration(2 * time.Hour),
|
||||
},
|
||||
|
||||
// Unfortunately, some misbehaved TCP implementations fail to respond to
|
||||
// a probe segment unless it contains data.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1122#section-4.2.3.6.
|
||||
dhcpv4.OptGeneric(dhcpv4.OptionTCPKeepaliveGarbage, []byte{0x01}),
|
||||
|
||||
// Values From Configuration
|
||||
|
||||
dhcpv4.OptionRouter.Code(): netutil.CloneIP(conf.subnet.IP),
|
||||
dhcpv4.OptionSubnetMask.Code(): dhcpv4.IPMask(conf.subnet.Mask).ToBytes(),
|
||||
}
|
||||
// Set the Router Option to working subnet's IP since it's initialized
|
||||
// with the address of the gateway.
|
||||
dhcpv4.OptRouter(conf.subnet.IP),
|
||||
|
||||
dhcpv4.OptSubnetMask(conf.subnet.Mask),
|
||||
)
|
||||
|
||||
// Set values for explicitly configured options.
|
||||
explicit = dhcpv4.Options{}
|
||||
for i, o := range conf.Options {
|
||||
opt, err := parseDHCPOption(o)
|
||||
code, val, err := parseDHCPOption(o)
|
||||
if err != nil {
|
||||
log.Error("dhcpv4: bad option string at index %d: %s", i, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
opts.Update(opt)
|
||||
explicit.Update(dhcpv4.Option{Code: code, Value: val})
|
||||
// Remove those from the implicit options.
|
||||
delete(implicit, code.Code())
|
||||
}
|
||||
|
||||
return opts
|
||||
log.Debug("dhcpv4: implicit options:\n%s", implicit.Summary(nil))
|
||||
log.Debug("dhcpv4: explicit options:\n%s", explicit.Summary(nil))
|
||||
|
||||
if len(explicit) == 0 {
|
||||
explicit = nil
|
||||
}
|
||||
|
||||
return implicit, explicit
|
||||
}
|
||||
|
||||
@@ -7,171 +7,260 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseOpt(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
in string
|
||||
wantCode dhcpv4.OptionCode
|
||||
wantVal dhcpv4.OptionValue
|
||||
wantErrMsg string
|
||||
wantOpt dhcpv4.Option
|
||||
}{{
|
||||
name: "hex_success",
|
||||
in: "6 hex c0a80101c0a80102",
|
||||
name: "hex_success",
|
||||
in: "6 hex c0a80101c0a80102",
|
||||
wantCode: dhcpv4.GenericOptionCode(6),
|
||||
wantVal: dhcpv4.OptionGeneric{Data: []byte{
|
||||
0xC0, 0xA8, 0x01, 0x01,
|
||||
0xC0, 0xA8, 0x01, 0x02,
|
||||
}},
|
||||
wantErrMsg: "",
|
||||
wantOpt: dhcpv4.OptDNS(
|
||||
net.IP{0xC0, 0xA8, 0x01, 0x01},
|
||||
net.IP{0xC0, 0xA8, 0x01, 0x02},
|
||||
),
|
||||
}, {
|
||||
name: "ip_success",
|
||||
in: "6 ip 1.2.3.4",
|
||||
wantCode: dhcpv4.GenericOptionCode(6),
|
||||
wantVal: dhcpv4.IP(net.IP{0x01, 0x02, 0x03, 0x04}),
|
||||
wantErrMsg: "",
|
||||
wantOpt: dhcpv4.OptDNS(
|
||||
net.IP{0x01, 0x02, 0x03, 0x04},
|
||||
),
|
||||
}, {
|
||||
name: "ip_fail_v6",
|
||||
in: "6 ip ::1234",
|
||||
wantErrMsg: "invalid option string \"6 ip ::1234\": bad ipv4 address \"::1234\"",
|
||||
wantOpt: dhcpv4.Option{},
|
||||
}, {
|
||||
name: "ips_success",
|
||||
in: "6 ips 192.168.1.1,192.168.1.2",
|
||||
name: "ips_success",
|
||||
in: "6 ips 192.168.1.1,192.168.1.2",
|
||||
wantCode: dhcpv4.GenericOptionCode(6),
|
||||
wantVal: dhcpv4.IPs([]net.IP{
|
||||
{0xC0, 0xA8, 0x01, 0x01},
|
||||
{0xC0, 0xA8, 0x01, 0x02},
|
||||
}),
|
||||
wantErrMsg: "",
|
||||
wantOpt: dhcpv4.OptDNS(
|
||||
net.IP{0xC0, 0xA8, 0x01, 0x01},
|
||||
net.IP{0xC0, 0xA8, 0x01, 0x02},
|
||||
),
|
||||
}, {
|
||||
name: "text_success",
|
||||
in: "252 text http://192.168.1.1/",
|
||||
wantCode: dhcpv4.GenericOptionCode(252),
|
||||
wantVal: dhcpv4.String("http://192.168.1.1/"),
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "del_success",
|
||||
in: "61 del",
|
||||
wantCode: dhcpv4.GenericOptionCode(dhcpv4.OptionClientIdentifier),
|
||||
wantVal: dhcpv4.OptionGeneric{Data: nil},
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "bool_success",
|
||||
in: "19 bool true",
|
||||
wantCode: dhcpv4.GenericOptionCode(dhcpv4.OptionIPForwarding),
|
||||
wantVal: dhcpv4.OptionGeneric{Data: []byte{0x01}},
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "bool_success_false",
|
||||
in: "19 bool F",
|
||||
wantCode: dhcpv4.GenericOptionCode(dhcpv4.OptionIPForwarding),
|
||||
wantVal: dhcpv4.OptionGeneric{Data: []byte{0x00}},
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "dur_success",
|
||||
in: "24 dur 2h5s",
|
||||
wantCode: dhcpv4.GenericOptionCode(dhcpv4.OptionPathMTUAgingTimeout),
|
||||
wantVal: dhcpv4.Duration(2*time.Hour + 5*time.Second),
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "u8_success",
|
||||
in: "23 u8 64",
|
||||
wantCode: dhcpv4.GenericOptionCode(dhcpv4.OptionDefaultIPTTL),
|
||||
wantVal: dhcpv4.OptionGeneric{Data: []byte{0x40}},
|
||||
wantErrMsg: "",
|
||||
}, {
|
||||
name: "u16_success",
|
||||
in: "22 u16 1234",
|
||||
wantCode: dhcpv4.GenericOptionCode(dhcpv4.OptionMaximumDatagramAssemblySize),
|
||||
wantVal: dhcpv4.Uint16(1234),
|
||||
wantErrMsg: "",
|
||||
wantOpt: dhcpv4.OptGeneric(
|
||||
dhcpv4.GenericOptionCode(252),
|
||||
[]byte("http://192.168.1.1/"),
|
||||
),
|
||||
}, {
|
||||
name: "bad_parts",
|
||||
in: "6 ip",
|
||||
wantErrMsg: `invalid option string "6 ip": need at least three fields`,
|
||||
wantOpt: dhcpv4.Option{},
|
||||
wantCode: nil,
|
||||
wantVal: nil,
|
||||
wantErrMsg: `invalid option string "6 ip": bad option format`,
|
||||
}, {
|
||||
name: "bad_code",
|
||||
in: "256 ip 1.1.1.1",
|
||||
name: "bad_code",
|
||||
in: "256 ip 1.1.1.1",
|
||||
wantCode: nil,
|
||||
wantVal: nil,
|
||||
wantErrMsg: `invalid option string "256 ip 1.1.1.1": parsing option code: ` +
|
||||
`strconv.ParseUint: parsing "256": value out of range`,
|
||||
wantOpt: dhcpv4.Option{},
|
||||
}, {
|
||||
name: "bad_type",
|
||||
in: "6 bad 1.1.1.1",
|
||||
wantCode: nil,
|
||||
wantVal: nil,
|
||||
wantErrMsg: `invalid option string "6 bad 1.1.1.1": unknown option type "bad"`,
|
||||
wantOpt: dhcpv4.Option{},
|
||||
}, {
|
||||
name: "hex_error",
|
||||
in: "6 hex ZZZ",
|
||||
name: "hex_error",
|
||||
in: "6 hex ZZZ",
|
||||
wantCode: nil,
|
||||
wantVal: nil,
|
||||
wantErrMsg: `invalid option string "6 hex ZZZ": decoding hex: ` +
|
||||
`encoding/hex: invalid byte: U+005A 'Z'`,
|
||||
wantOpt: dhcpv4.Option{},
|
||||
}, {
|
||||
name: "ip_error",
|
||||
in: "6 ip 1.2.3.x",
|
||||
wantCode: nil,
|
||||
wantVal: nil,
|
||||
wantErrMsg: "invalid option string \"6 ip 1.2.3.x\": bad ipv4 address \"1.2.3.x\"",
|
||||
wantOpt: dhcpv4.Option{},
|
||||
}, {
|
||||
name: "ips_error",
|
||||
in: "6 ips 192.168.1.1,192.168.1.x",
|
||||
name: "ip_error_v6",
|
||||
in: "6 ip ::1234",
|
||||
wantCode: nil,
|
||||
wantVal: nil,
|
||||
wantErrMsg: "invalid option string \"6 ip ::1234\": bad ipv4 address \"::1234\"",
|
||||
}, {
|
||||
name: "ips_error",
|
||||
in: "6 ips 192.168.1.1,192.168.1.x",
|
||||
wantCode: nil,
|
||||
wantVal: nil,
|
||||
wantErrMsg: "invalid option string \"6 ips 192.168.1.1,192.168.1.x\": " +
|
||||
"parsing ip at index 1: bad ipv4 address \"192.168.1.x\"",
|
||||
wantOpt: dhcpv4.Option{},
|
||||
}, {
|
||||
name: "bool_error",
|
||||
in: "19 bool yes",
|
||||
wantCode: nil,
|
||||
wantVal: nil,
|
||||
wantErrMsg: "invalid option string \"19 bool yes\": decoding bool: " +
|
||||
"strconv.ParseBool: parsing \"yes\": invalid syntax",
|
||||
}, {
|
||||
name: "dur_error",
|
||||
in: "24 dur 3y",
|
||||
wantCode: nil,
|
||||
wantVal: nil,
|
||||
wantErrMsg: "invalid option string \"24 dur 3y\": decoding dur: " +
|
||||
"unmarshaling duration: time: unknown unit \"y\" in duration \"3y\"",
|
||||
}, {
|
||||
name: "u8_error",
|
||||
in: "23 u8 256",
|
||||
wantCode: nil,
|
||||
wantVal: nil,
|
||||
wantErrMsg: "invalid option string \"23 u8 256\": decoding u8: " +
|
||||
"strconv.ParseUint: parsing \"256\": value out of range",
|
||||
}, {
|
||||
name: "u16_error",
|
||||
in: "23 u16 65536",
|
||||
wantCode: nil,
|
||||
wantVal: nil,
|
||||
wantErrMsg: "invalid option string \"23 u16 65536\": decoding u16: " +
|
||||
"strconv.ParseUint: parsing \"65536\": value out of range",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
opt, err := parseDHCPOption(tc.in)
|
||||
if tc.wantErrMsg != "" {
|
||||
require.Error(t, err)
|
||||
code, val, err := parseDHCPOption(tc.in)
|
||||
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
|
||||
|
||||
assert.Equal(t, tc.wantErrMsg, err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.wantOpt.Code.Code(), opt.Code.Code())
|
||||
assert.Equal(t, tc.wantOpt.Value.ToBytes(), opt.Value.ToBytes())
|
||||
assert.Equal(t, tc.wantCode, code)
|
||||
assert.Equal(t, tc.wantVal, val)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareOptions(t *testing.T) {
|
||||
allDefault := dhcpv4.Options{
|
||||
dhcpv4.OptionNonLocalSourceRouting.Code(): []byte{0},
|
||||
dhcpv4.OptionDefaultIPTTL.Code(): []byte{64},
|
||||
dhcpv4.OptionPerformMaskDiscovery.Code(): []byte{0},
|
||||
dhcpv4.OptionMaskSupplier.Code(): []byte{0},
|
||||
dhcpv4.OptionPerformRouterDiscovery.Code(): []byte{1},
|
||||
dhcpv4.OptionRouterSolicitationAddress.Code(): []byte{224, 0, 0, 2},
|
||||
dhcpv4.OptionBroadcastAddress.Code(): []byte{255, 255, 255, 255},
|
||||
dhcpv4.OptionTrailerEncapsulation.Code(): []byte{0},
|
||||
dhcpv4.OptionEthernetEncapsulation.Code(): []byte{0},
|
||||
dhcpv4.OptionTCPKeepaliveInterval.Code(): []byte{0, 0, 0, 0},
|
||||
dhcpv4.OptionTCPKeepaliveGarbage.Code(): []byte{0},
|
||||
}
|
||||
oneIP, otherIP := net.IP{1, 2, 3, 4}, net.IP{5, 6, 7, 8}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
opts []string
|
||||
checks dhcpv4.Options
|
||||
name string
|
||||
wantExplicit dhcpv4.Options
|
||||
opts []string
|
||||
}{{
|
||||
name: "all_default",
|
||||
checks: allDefault,
|
||||
name: "all_default",
|
||||
wantExplicit: nil,
|
||||
opts: nil,
|
||||
}, {
|
||||
name: "configured_ip",
|
||||
wantExplicit: dhcpv4.OptionsFromList(
|
||||
dhcpv4.OptBroadcastAddress(oneIP),
|
||||
),
|
||||
opts: []string{
|
||||
fmt.Sprintf("%d ip %s", dhcpv4.OptionBroadcastAddress, oneIP),
|
||||
},
|
||||
checks: dhcpv4.Options{
|
||||
dhcpv4.OptionBroadcastAddress.Code(): oneIP,
|
||||
},
|
||||
}, {
|
||||
name: "configured_ips",
|
||||
wantExplicit: dhcpv4.OptionsFromList(
|
||||
dhcpv4.Option{
|
||||
Code: dhcpv4.OptionDomainNameServer,
|
||||
Value: dhcpv4.IPs{oneIP, otherIP},
|
||||
},
|
||||
),
|
||||
opts: []string{
|
||||
fmt.Sprintf("%d ips %s,%s", dhcpv4.OptionDomainNameServer, oneIP, otherIP),
|
||||
},
|
||||
checks: dhcpv4.Options{
|
||||
dhcpv4.OptionDomainNameServer.Code(): append(oneIP, otherIP...),
|
||||
},
|
||||
}, {
|
||||
name: "configured_bad",
|
||||
name: "configured_bad",
|
||||
wantExplicit: nil,
|
||||
opts: []string{
|
||||
"19 bool yes",
|
||||
"24 dur 3y",
|
||||
"23 u8 256",
|
||||
"23 u16 65536",
|
||||
"20 hex",
|
||||
"23 hex abc",
|
||||
"32 ips 1,2,3,4",
|
||||
"28 256.256.256.256",
|
||||
},
|
||||
checks: allDefault,
|
||||
}, {
|
||||
name: "configured_del",
|
||||
wantExplicit: dhcpv4.OptionsFromList(
|
||||
dhcpv4.OptBroadcastAddress(nil),
|
||||
),
|
||||
opts: []string{
|
||||
"28 del",
|
||||
},
|
||||
}, {
|
||||
name: "rewritten_del",
|
||||
wantExplicit: dhcpv4.OptionsFromList(
|
||||
dhcpv4.OptBroadcastAddress(netutil.IPv4bcast()),
|
||||
),
|
||||
opts: []string{
|
||||
"28 del",
|
||||
"28 ip 255.255.255.255",
|
||||
},
|
||||
}, {
|
||||
name: "configured_and_del",
|
||||
wantExplicit: dhcpv4.OptionsFromList(
|
||||
dhcpv4.Option{
|
||||
Code: dhcpv4.OptionGeoConf,
|
||||
Value: dhcpv4.String("cba"),
|
||||
},
|
||||
),
|
||||
opts: []string{
|
||||
"123 text abc",
|
||||
"123 del",
|
||||
"123 text cba",
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
opts := prepareOptions(V4ServerConf{
|
||||
implicit, explicit := prepareOptions(V4ServerConf{
|
||||
// Just to avoid nil pointer dereference.
|
||||
subnet: &net.IPNet{},
|
||||
Options: tc.opts,
|
||||
})
|
||||
for c, v := range tc.checks {
|
||||
optVal := opts.Get(dhcpv4.GenericOptionCode(c))
|
||||
require.NotNil(t, optVal)
|
||||
|
||||
assert.Len(t, optVal, len(v))
|
||||
assert.Equal(t, v, optVal)
|
||||
assert.Equal(t, tc.wantExplicit, explicit)
|
||||
|
||||
for c := range explicit {
|
||||
assert.NotContains(t, implicit, c)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -65,39 +65,42 @@ func hwAddrToLinkLayerAddr(hwa net.HardwareAddr) (lla []byte, err error) {
|
||||
}
|
||||
|
||||
// Create an ICMPv6.RouterAdvertisement packet with all necessary options.
|
||||
// Data scheme:
|
||||
//
|
||||
// ICMPv6:
|
||||
// type[1]
|
||||
// code[1]
|
||||
// chksum[2]
|
||||
// body (RouterAdvertisement):
|
||||
// Cur Hop Limit[1]
|
||||
// Flags[1]: MO......
|
||||
// Router Lifetime[2]
|
||||
// Reachable Time[4]
|
||||
// Retrans Timer[4]
|
||||
// Option=Prefix Information(3):
|
||||
// Type[1]
|
||||
// Length * 8bytes[1]
|
||||
// Prefix Length[1]
|
||||
// Flags[1]: LA......
|
||||
// Valid Lifetime[4]
|
||||
// Preferred Lifetime[4]
|
||||
// Reserved[4]
|
||||
// Prefix[16]
|
||||
// Option=MTU(5):
|
||||
// Type[1]
|
||||
// Length * 8bytes[1]
|
||||
// Reserved[2]
|
||||
// MTU[4]
|
||||
// Option=Source link-layer address(1):
|
||||
// Link-Layer Address[8/24]
|
||||
// Option=Recursive DNS Server(25):
|
||||
// Type[1]
|
||||
// Length * 8bytes[1]
|
||||
// Reserved[2]
|
||||
// Lifetime[4]
|
||||
// Addresses of IPv6 Recursive DNS Servers[16]
|
||||
// ICMPv6:
|
||||
// - type[1]
|
||||
// - code[1]
|
||||
// - chksum[2]
|
||||
// - body (RouterAdvertisement):
|
||||
// - Cur Hop Limit[1]
|
||||
// - Flags[1]: MO......
|
||||
// - Router Lifetime[2]
|
||||
// - Reachable Time[4]
|
||||
// - Retrans Timer[4]
|
||||
// - Option=Prefix Information(3):
|
||||
// - Type[1]
|
||||
// - Length * 8bytes[1]
|
||||
// - Prefix Length[1]
|
||||
// - Flags[1]: LA......
|
||||
// - Valid Lifetime[4]
|
||||
// - Preferred Lifetime[4]
|
||||
// - Reserved[4]
|
||||
// - Prefix[16]
|
||||
// - Option=MTU(5):
|
||||
// - Type[1]
|
||||
// - Length * 8bytes[1]
|
||||
// - Reserved[2]
|
||||
// - MTU[4]
|
||||
// - Option=Source link-layer address(1):
|
||||
// - Link-Layer Address[8/24]
|
||||
// - Option=Recursive DNS Server(25):
|
||||
// - Type[1]
|
||||
// - Length * 8bytes[1]
|
||||
// - Reserved[2]
|
||||
// - Lifetime[4]
|
||||
// - Addresses of IPv6 Recursive DNS Servers[16]
|
||||
//
|
||||
// TODO(a.garipov): Replace with an existing implementation from a dependency.
|
||||
func createICMPv6RAPacket(params icmpv6RA) (data []byte, err error) {
|
||||
var lla []byte
|
||||
lla, err = hwAddrToLinkLayerAddr(params.sourceLinkLayerAddress)
|
||||
|
||||
@@ -71,12 +71,12 @@ type V4ServerConf struct {
|
||||
// gateway.
|
||||
subnet *net.IPNet
|
||||
|
||||
// notify is a way to signal to other components that leases have
|
||||
// change. notify must be called outside of locked sections, since the
|
||||
// notify is a way to signal to other components that leases have been
|
||||
// changed. notify must be called outside of locked sections, since the
|
||||
// clients might want to get the new data.
|
||||
//
|
||||
// TODO(a.garipov): This is utter madness and must be refactored. It
|
||||
// just begs for deadlock bugs and other nastiness.
|
||||
// TODO(a.garipov): This is utter madness and must be refactored. It just
|
||||
// begs for deadlock bugs and other nastiness.
|
||||
notify func(uint32)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/go-ping/ping"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
//lint:ignore SA1019 See the TODO in go.mod.
|
||||
"github.com/mdlayher/raw"
|
||||
@@ -32,6 +33,17 @@ type v4Server struct {
|
||||
conf V4ServerConf
|
||||
srv *server4.Server
|
||||
|
||||
// implicitOpts are the options listed in Appendix A of RFC 2131 initialized
|
||||
// with default values. It must not have intersections with [explicitOpts].
|
||||
implicitOpts dhcpv4.Options
|
||||
|
||||
// explicitOpts are the options parsed from the configuration. It must not
|
||||
// have intersections with [implicitOpts].
|
||||
explicitOpts dhcpv4.Options
|
||||
|
||||
// leasesLock protects leases, leaseHosts, and leasedOffsets.
|
||||
leasesLock sync.Mutex
|
||||
|
||||
// leasedOffsets contains offsets from conf.ipRange.start that have been
|
||||
// leased.
|
||||
leasedOffsets *bitSet
|
||||
@@ -41,12 +53,6 @@ type v4Server struct {
|
||||
|
||||
// leases contains all dynamic and static leases.
|
||||
leases []*Lease
|
||||
|
||||
// leasesLock protects leases, leaseHosts, and leasedOffsets.
|
||||
leasesLock sync.Mutex
|
||||
|
||||
// options holds predefined DHCP options to return to clients.
|
||||
options dhcpv4.Options
|
||||
}
|
||||
|
||||
// WriteDiskConfig4 - write configuration
|
||||
@@ -252,11 +258,11 @@ func (s *v4Server) rmLeaseByIndex(i int) {
|
||||
// Remove a dynamic lease with the same properties
|
||||
// Return error if a static lease is found
|
||||
func (s *v4Server) rmDynamicLease(lease *Lease) (err error) {
|
||||
for i := 0; i < len(s.leases); i++ {
|
||||
l := s.leases[i]
|
||||
for i, l := range s.leases {
|
||||
isStatic := l.IsStatic()
|
||||
|
||||
if bytes.Equal(l.HWAddr, lease.HWAddr) {
|
||||
if l.IsStatic() {
|
||||
if bytes.Equal(l.HWAddr, lease.HWAddr) || l.IP.Equal(lease.IP) {
|
||||
if isStatic {
|
||||
return errors.Error("static lease already exists")
|
||||
}
|
||||
|
||||
@@ -268,20 +274,7 @@ func (s *v4Server) rmDynamicLease(lease *Lease) (err error) {
|
||||
l = s.leases[i]
|
||||
}
|
||||
|
||||
if l.IP.Equal(lease.IP) {
|
||||
if l.IsStatic() {
|
||||
return errors.Error("static lease already exists")
|
||||
}
|
||||
|
||||
s.rmLeaseByIndex(i)
|
||||
if i == len(s.leases) {
|
||||
break
|
||||
}
|
||||
|
||||
l = s.leases[i]
|
||||
}
|
||||
|
||||
if l.Hostname == lease.Hostname {
|
||||
if !isStatic && l.Hostname == lease.Hostname {
|
||||
l.Hostname = ""
|
||||
}
|
||||
}
|
||||
@@ -289,6 +282,10 @@ func (s *v4Server) rmDynamicLease(lease *Lease) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ErrDupHostname is returned by addLease when the added lease has a not empty
|
||||
// non-unique hostname.
|
||||
const ErrDupHostname = errors.Error("hostname is not unique")
|
||||
|
||||
// addLease adds a dynamic or static lease.
|
||||
func (s *v4Server) addLease(l *Lease) (err error) {
|
||||
r := s.conf.ipRange
|
||||
@@ -304,13 +301,17 @@ func (s *v4Server) addLease(l *Lease) (err error) {
|
||||
return fmt.Errorf("lease %s (%s) out of range, not adding", l.IP, l.HWAddr)
|
||||
}
|
||||
|
||||
s.leases = append(s.leases, l)
|
||||
s.leasedOffsets.set(offset, true)
|
||||
|
||||
if l.Hostname != "" {
|
||||
if s.leaseHosts.Has(l.Hostname) {
|
||||
return ErrDupHostname
|
||||
}
|
||||
|
||||
s.leaseHosts.Add(l.Hostname)
|
||||
}
|
||||
|
||||
s.leases = append(s.leases, l)
|
||||
s.leasedOffsets.set(offset, true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -365,10 +366,11 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
|
||||
return fmt.Errorf("validating hostname: %w", err)
|
||||
}
|
||||
|
||||
// Don't check for hostname uniqueness, since we try to emulate
|
||||
// dnsmasq here, which means that rmDynamicLease below will
|
||||
// simply empty the hostname of the dynamic lease if there even
|
||||
// is one.
|
||||
// Don't check for hostname uniqueness, since we try to emulate dnsmasq
|
||||
// here, which means that rmDynamicLease below will simply empty the
|
||||
// hostname of the dynamic lease if there even is one. In case a static
|
||||
// lease with the same name already exists, addLease will return an
|
||||
// error and the lease won't be added.
|
||||
|
||||
l.Hostname = hostname
|
||||
}
|
||||
@@ -421,19 +423,19 @@ func (s *v4Server) RemoveStaticLease(l *Lease) (err error) {
|
||||
return fmt.Errorf("validating lease: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
s.conf.notify(LeaseChangedRemovedStatic)
|
||||
}()
|
||||
|
||||
s.leasesLock.Lock()
|
||||
err = s.rmLease(l)
|
||||
if err != nil {
|
||||
s.leasesLock.Unlock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
return err
|
||||
}
|
||||
s.leasesLock.Unlock()
|
||||
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
s.conf.notify(LeaseChangedRemovedStatic)
|
||||
|
||||
return nil
|
||||
return s.rmLease(l)
|
||||
}
|
||||
|
||||
// addrAvailable sends an ICP request to the specified IP address. It returns
|
||||
@@ -523,11 +525,7 @@ func (s *v4Server) findExpiredLease() int {
|
||||
// reserveLease reserves a lease for a client by its MAC-address. It returns
|
||||
// nil if it couldn't allocate a new lease.
|
||||
func (s *v4Server) reserveLease(mac net.HardwareAddr) (l *Lease, err error) {
|
||||
l = &Lease{
|
||||
HWAddr: make([]byte, len(mac)),
|
||||
}
|
||||
|
||||
copy(l.HWAddr, mac)
|
||||
l = &Lease{HWAddr: slices.Clone(mac)}
|
||||
|
||||
l.IP = s.nextIP()
|
||||
if l.IP == nil {
|
||||
@@ -549,21 +547,34 @@ func (s *v4Server) reserveLease(mac net.HardwareAddr) (l *Lease, err error) {
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (s *v4Server) commitLease(l *Lease) {
|
||||
l.Expiry = time.Now().Add(s.conf.leaseTime)
|
||||
// commitLease refreshes l's values. It takes the desired hostname into account
|
||||
// when setting it into the lease, but generates a unique one if the provided
|
||||
// can't be used.
|
||||
func (s *v4Server) commitLease(l *Lease, hostname string) {
|
||||
prev := l.Hostname
|
||||
hostname = s.validHostnameForClient(hostname, l.IP)
|
||||
|
||||
func() {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
if s.leaseHosts.Has(hostname) {
|
||||
log.Info("dhcpv4: hostname %q already exists", hostname)
|
||||
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
|
||||
if l.Hostname != "" {
|
||||
s.leaseHosts.Add(l.Hostname)
|
||||
if prev == "" {
|
||||
// The lease is just allocated due to DHCPDISCOVER.
|
||||
hostname = aghnet.GenerateHostname(l.IP)
|
||||
} else {
|
||||
hostname = prev
|
||||
}
|
||||
}()
|
||||
}
|
||||
if l.Hostname != hostname {
|
||||
l.Hostname = hostname
|
||||
}
|
||||
|
||||
s.conf.notify(LeaseChangedAdded)
|
||||
l.Expiry = time.Now().Add(s.conf.leaseTime)
|
||||
if prev != "" && prev != l.Hostname {
|
||||
s.leaseHosts.Del(prev)
|
||||
}
|
||||
if l.Hostname != "" {
|
||||
s.leaseHosts.Add(l.Hostname)
|
||||
}
|
||||
}
|
||||
|
||||
// allocateLease allocates a new lease for the MAC address. If there are no IP
|
||||
@@ -585,8 +596,8 @@ func (s *v4Server) allocateLease(mac net.HardwareAddr) (l *Lease, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// processDiscover is the handler for the DHCP Discover request.
|
||||
func (s *v4Server) processDiscover(req, resp *dhcpv4.DHCPv4) (l *Lease, err error) {
|
||||
// handleDiscover is the handler for the DHCP Discover request.
|
||||
func (s *v4Server) handleDiscover(req, resp *dhcpv4.DHCPv4) (l *Lease, err error) {
|
||||
mac := req.ClientHWAddr
|
||||
|
||||
defer s.conf.notify(LeaseChangedDBStore)
|
||||
@@ -620,33 +631,25 @@ func (s *v4Server) processDiscover(req, resp *dhcpv4.DHCPv4) (l *Lease, err erro
|
||||
return l, nil
|
||||
}
|
||||
|
||||
type optFQDN struct {
|
||||
name string
|
||||
}
|
||||
// OptionFQDN returns a DHCPv4 option for sending the FQDN to the client
|
||||
// requested another hostname.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc4702.
|
||||
func OptionFQDN(fqdn string) (opt dhcpv4.Option) {
|
||||
optData := []byte{
|
||||
// Set only S and O DHCP client FQDN option flags.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc4702#section-2.1.
|
||||
1<<0 | 1<<1,
|
||||
// The RCODE fields should be set to 0xFF in the server responses.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc4702#section-2.2.
|
||||
0xFF,
|
||||
0xFF,
|
||||
}
|
||||
optData = append(optData, fqdn...)
|
||||
|
||||
func (o *optFQDN) String() string {
|
||||
return "optFQDN"
|
||||
}
|
||||
|
||||
// flags[1]
|
||||
// A-RR[1]
|
||||
// PTR-RR[1]
|
||||
// name[]
|
||||
func (o *optFQDN) ToBytes() []byte {
|
||||
b := make([]byte, 3+len(o.name))
|
||||
i := 0
|
||||
|
||||
b[i] = 0x03 // f_server_overrides | f_server
|
||||
i++
|
||||
|
||||
b[i] = 255 // A-RR
|
||||
i++
|
||||
|
||||
b[i] = 255 // PTR-RR
|
||||
i++
|
||||
|
||||
copy(b[i:], []byte(o.name))
|
||||
return b
|
||||
return dhcpv4.OptGeneric(dhcpv4.OptionFQDN, optData)
|
||||
}
|
||||
|
||||
// checkLease checks if the pair of mac and ip is already leased. The mismatch
|
||||
@@ -676,68 +679,177 @@ func (s *v4Server) checkLease(mac net.HardwareAddr, ip net.IP) (lease *Lease, mi
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// processRequest is the handler for the DHCP Request request.
|
||||
func (s *v4Server) processRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needsReply bool) {
|
||||
// handleSelecting handles the DHCPREQUEST generated during SELECTING state.
|
||||
func (s *v4Server) handleSelecting(
|
||||
req *dhcpv4.DHCPv4,
|
||||
reqIP net.IP,
|
||||
sid net.IP,
|
||||
) (l *Lease, needsReply bool) {
|
||||
// Client inserts the address of the selected server in server identifier,
|
||||
// ciaddr MUST be zero.
|
||||
mac := req.ClientHWAddr
|
||||
reqIP := req.RequestedIPAddress()
|
||||
if reqIP == nil {
|
||||
reqIP = req.ClientIPAddr
|
||||
}
|
||||
if !sid.Equal(s.conf.dnsIPAddrs[0]) {
|
||||
log.Debug("dhcpv4: bad server identifier in req msg for %s: %s", mac, sid)
|
||||
|
||||
sid := req.ServerIdentifier()
|
||||
if len(sid) != 0 && !sid.Equal(s.conf.dnsIPAddrs[0]) {
|
||||
log.Debug("dhcpv4: bad OptionServerIdentifier in req msg for %s", mac)
|
||||
return nil, false
|
||||
} else if ciaddr := req.ClientIPAddr; ciaddr != nil && !ciaddr.IsUnspecified() {
|
||||
log.Debug("dhcpv4: non-zero ciaddr in selecting req msg for %s", mac)
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Requested IP address MUST be filled in with the yiaddr value from the
|
||||
// chosen DHCPOFFER.
|
||||
if ip4 := reqIP.To4(); ip4 == nil {
|
||||
log.Debug("dhcpv4: bad OptionRequestedIPAddress in req msg for %s", mac)
|
||||
log.Debug("dhcpv4: bad requested address in req msg for %s: %s", mac, reqIP)
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var mismatch bool
|
||||
if lease, mismatch = s.checkLease(mac, reqIP); mismatch {
|
||||
if l, mismatch = s.checkLease(mac, reqIP); mismatch {
|
||||
return nil, true
|
||||
}
|
||||
|
||||
if lease == nil {
|
||||
} else if l == nil {
|
||||
log.Debug("dhcpv4: no reserved lease for %s", mac)
|
||||
}
|
||||
|
||||
return l, true
|
||||
}
|
||||
|
||||
// handleInitReboot handles the DHCPREQUEST generated during INIT-REBOOT state.
|
||||
func (s *v4Server) handleInitReboot(req *dhcpv4.DHCPv4, reqIP net.IP) (l *Lease, needsReply bool) {
|
||||
mac := req.ClientHWAddr
|
||||
|
||||
if ip4 := reqIP.To4(); ip4 == nil {
|
||||
log.Debug("dhcpv4: bad requested address in req msg for %s: %s", mac, reqIP)
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// ciaddr MUST be zero. The client is seeking to verify a previously
|
||||
// allocated, cached configuration.
|
||||
if ciaddr := req.ClientIPAddr; ciaddr != nil && !ciaddr.IsUnspecified() {
|
||||
log.Debug("dhcpv4: non-zero ciaddr in init-reboot req msg for %s", mac)
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if !s.conf.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.
|
||||
log.Debug("dhcpv4: wrong subnet in init-reboot req msg for %s: %s", mac, reqIP)
|
||||
|
||||
return nil, true
|
||||
}
|
||||
|
||||
if !lease.IsStatic() {
|
||||
cliHostname := req.HostName()
|
||||
hostname := s.validHostnameForClient(cliHostname, reqIP)
|
||||
if hostname != lease.Hostname && s.leaseHosts.Has(hostname) {
|
||||
log.Info("dhcpv4: hostname %q already exists", hostname)
|
||||
lease.Hostname = ""
|
||||
} else {
|
||||
lease.Hostname = hostname
|
||||
}
|
||||
var mismatch bool
|
||||
if l, mismatch = s.checkLease(mac, reqIP); mismatch {
|
||||
return nil, true
|
||||
} else if l == nil {
|
||||
// If the DHCP server has no record of this client, then it MUST remain
|
||||
// silent, and MAY output a warning to the network administrator.
|
||||
log.Info("dhcpv4: warning: no existing lease for %s", mac)
|
||||
|
||||
s.commitLease(lease)
|
||||
} else if lease.Hostname != "" {
|
||||
o := &optFQDN{
|
||||
name: lease.Hostname,
|
||||
}
|
||||
fqdn := dhcpv4.Option{
|
||||
Code: dhcpv4.OptionFQDN,
|
||||
Value: o,
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
resp.UpdateOption(fqdn)
|
||||
return l, true
|
||||
}
|
||||
|
||||
// handleRenew handles the DHCPREQUEST generated during RENEWING or REBINDING
|
||||
// state.
|
||||
func (s *v4Server) handleRenew(req *dhcpv4.DHCPv4) (l *Lease, needsReply bool) {
|
||||
mac := req.ClientHWAddr
|
||||
|
||||
// ciaddr MUST be filled in with client's IP address.
|
||||
ciaddr := req.ClientIPAddr
|
||||
if ciaddr == nil || ciaddr.IsUnspecified() || ciaddr.To4() == nil {
|
||||
log.Debug("dhcpv4: bad ciaddr in renew req msg for %s: %s", mac, ciaddr)
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var mismatch bool
|
||||
if l, mismatch = s.checkLease(mac, ciaddr); mismatch {
|
||||
return nil, true
|
||||
} else if l == nil {
|
||||
// If the DHCP server has no record of this client, then it MUST remain
|
||||
// silent, and MAY output a warning to the network administrator.
|
||||
log.Info("dhcpv4: warning: no existing lease for %s", mac)
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return l, true
|
||||
}
|
||||
|
||||
// handleByRequestType handles the DHCPREQUEST according to the state during
|
||||
// which it's generated by client.
|
||||
func (s *v4Server) handleByRequestType(req *dhcpv4.DHCPv4) (lease *Lease, needsReply bool) {
|
||||
reqIP, sid := req.RequestedIPAddress(), req.ServerIdentifier()
|
||||
|
||||
if sid != nil && !sid.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.
|
||||
return s.handleSelecting(req, reqIP, sid)
|
||||
}
|
||||
|
||||
if reqIP != nil && !reqIP.IsUnspecified() {
|
||||
// Requested IP address option MUST be filled in with client's notion of
|
||||
// its previously assigned address.
|
||||
return s.handleInitReboot(req, reqIP)
|
||||
}
|
||||
|
||||
// Server identifier MUST NOT be filled in, requested IP address option MUST
|
||||
// NOT be filled in.
|
||||
return s.handleRenew(req)
|
||||
}
|
||||
|
||||
// handleRequest is the handler for a DHCPREQUEST message.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc2131#section-4.3.2.
|
||||
func (s *v4Server) handleRequest(req, resp *dhcpv4.DHCPv4) (lease *Lease, needsReply bool) {
|
||||
lease, needsReply = s.handleByRequestType(req)
|
||||
if lease == nil {
|
||||
return nil, needsReply
|
||||
}
|
||||
|
||||
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
|
||||
|
||||
return lease, true
|
||||
hostname := req.HostName()
|
||||
isRequested := hostname != "" || req.ParameterRequestList().Has(dhcpv4.OptionHostName)
|
||||
|
||||
defer func() {
|
||||
s.conf.notify(LeaseChangedAdded)
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
}()
|
||||
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
if lease.IsStatic() {
|
||||
if lease.Hostname != "" {
|
||||
// TODO(e.burkov): This option is used to update the server's DNS
|
||||
// mapping. The option should only be answered when it has been
|
||||
// requested.
|
||||
resp.UpdateOption(OptionFQDN(lease.Hostname))
|
||||
}
|
||||
|
||||
return lease, needsReply
|
||||
}
|
||||
|
||||
s.commitLease(lease, hostname)
|
||||
|
||||
if isRequested {
|
||||
resp.UpdateOption(dhcpv4.OptHostName(lease.Hostname))
|
||||
}
|
||||
|
||||
return lease, needsReply
|
||||
}
|
||||
|
||||
// processRequest is the handler for the DHCP Decline request.
|
||||
func (s *v4Server) processDecline(req, resp *dhcpv4.DHCPv4) (err error) {
|
||||
// handleDecline is the handler for the DHCP Decline request.
|
||||
func (s *v4Server) handleDecline(req, resp *dhcpv4.DHCPv4) (err error) {
|
||||
s.conf.notify(LeaseChangedDBStore)
|
||||
|
||||
s.leasesLock.Lock()
|
||||
@@ -799,8 +911,8 @@ func (s *v4Server) processDecline(req, resp *dhcpv4.DHCPv4) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// processRelease is the handler for the DHCP Release request.
|
||||
func (s *v4Server) processRelease(req, resp *dhcpv4.DHCPv4) (err error) {
|
||||
// handleRelease is the handler for the DHCP Release request.
|
||||
func (s *v4Server) handleRelease(req, resp *dhcpv4.DHCPv4) (err error) {
|
||||
mac := req.ClientHWAddr
|
||||
reqIP := req.RequestedIPAddress()
|
||||
if reqIP == nil {
|
||||
@@ -841,7 +953,7 @@ func (s *v4Server) processRelease(req, resp *dhcpv4.DHCPv4) (err error) {
|
||||
// Return 1: OK
|
||||
// Return 0: error; reply with Nak
|
||||
// Return -1: error; don't reply
|
||||
func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
|
||||
func (s *v4Server) handle(req, resp *dhcpv4.DHCPv4) int {
|
||||
var err error
|
||||
|
||||
// Include server's identifier option since any reply should contain it.
|
||||
@@ -851,11 +963,11 @@ func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
|
||||
|
||||
// TODO(a.garipov): Refactor this into handlers.
|
||||
var l *Lease
|
||||
switch req.MessageType() {
|
||||
switch mt := req.MessageType(); mt {
|
||||
case dhcpv4.MessageTypeDiscover:
|
||||
l, err = s.processDiscover(req, resp)
|
||||
l, err = s.handleDiscover(req, resp)
|
||||
if err != nil {
|
||||
log.Error("dhcpv4: processing discover: %s", err)
|
||||
log.Error("dhcpv4: handling discover: %s", err)
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -865,7 +977,7 @@ func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
|
||||
}
|
||||
case dhcpv4.MessageTypeRequest:
|
||||
var toReply bool
|
||||
l, toReply = s.processRequest(req, resp)
|
||||
l, toReply = s.handleRequest(req, resp)
|
||||
if l == nil {
|
||||
if toReply {
|
||||
return 0
|
||||
@@ -873,16 +985,16 @@ func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
|
||||
return -1 // drop packet
|
||||
}
|
||||
case dhcpv4.MessageTypeDecline:
|
||||
err = s.processDecline(req, resp)
|
||||
err = s.handleDecline(req, resp)
|
||||
if err != nil {
|
||||
log.Error("dhcpv4: processing decline: %s", err)
|
||||
log.Error("dhcpv4: handling decline: %s", err)
|
||||
|
||||
return 0
|
||||
}
|
||||
case dhcpv4.MessageTypeRelease:
|
||||
err = s.processRelease(req, resp)
|
||||
err = s.handleRelease(req, resp)
|
||||
if err != nil {
|
||||
log.Error("dhcpv4: processing release: %s", err)
|
||||
log.Error("dhcpv4: handling release: %s", err)
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -892,30 +1004,42 @@ func (s *v4Server) process(req, resp *dhcpv4.DHCPv4) int {
|
||||
resp.YourIPAddr = netutil.CloneIP(l.IP)
|
||||
}
|
||||
|
||||
// Set IP address lease time for all DHCPOFFER messages and DHCPACK
|
||||
// messages replied for DHCPREQUEST.
|
||||
s.updateOptions(req, resp)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// updateOptions updates the options of the response in accordance with the
|
||||
// request and RFC 2131.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc2131#section-4.3.1.
|
||||
func (s *v4Server) updateOptions(req, resp *dhcpv4.DHCPv4) {
|
||||
// Set IP address lease time for all DHCPOFFER messages and DHCPACK messages
|
||||
// replied for DHCPREQUEST.
|
||||
//
|
||||
// TODO(e.burkov): Inspect why this is always set to configured value.
|
||||
resp.UpdateOption(dhcpv4.OptIPAddressLeaseTime(s.conf.leaseTime))
|
||||
|
||||
// Update values for each explicitly configured parameter requested by
|
||||
// client.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc2131#section-4.3.1.
|
||||
requested := req.ParameterRequestList()
|
||||
for _, code := range requested {
|
||||
if configured := s.options; configured.Has(code) {
|
||||
resp.UpdateOption(dhcpv4.OptGeneric(code, configured.Get(code)))
|
||||
// If the server recognizes the parameter as a parameter defined in the Host
|
||||
// Requirements Document, the server MUST include the default value for that
|
||||
// parameter.
|
||||
for _, code := range req.ParameterRequestList() {
|
||||
if val := s.implicitOpts.Get(code); val != nil {
|
||||
resp.UpdateOption(dhcpv4.OptGeneric(code, val))
|
||||
}
|
||||
}
|
||||
// Update the value of Domain Name Server option separately from others if
|
||||
// not assigned yet since its value is set after server's creating.
|
||||
if requested.Has(dhcpv4.OptionDomainNameServer) &&
|
||||
!resp.Options.Has(dhcpv4.OptionDomainNameServer) {
|
||||
resp.UpdateOption(dhcpv4.OptDNS(s.conf.dnsIPAddrs...))
|
||||
}
|
||||
|
||||
return 1
|
||||
// If the server has been explicitly configured with a default value for the
|
||||
// parameter or the parameter has a non-default value on the client's
|
||||
// subnet, the server MUST include that value in an appropriate option.
|
||||
for code, val := range s.explicitOpts {
|
||||
if val != nil {
|
||||
resp.Options[code] = val
|
||||
} else {
|
||||
// Delete options explicitly configured to be removed.
|
||||
delete(resp.Options, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// client(0.0.0.0:68) -> (Request:ClientMAC,Type=Discover,ClientID,ReqIP,HostName) -> server(255.255.255.255:67)
|
||||
@@ -941,6 +1065,7 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
|
||||
resp, err := dhcpv4.NewReplyFromRequest(req)
|
||||
if err != nil {
|
||||
log.Debug("dhcpv4: dhcpv4.New: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -951,7 +1076,7 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
|
||||
return
|
||||
}
|
||||
|
||||
r := s.process(req, resp)
|
||||
r := s.handle(req, resp)
|
||||
if r < 0 {
|
||||
return
|
||||
} else if r == 0 {
|
||||
@@ -961,6 +1086,12 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
|
||||
s.send(peer, conn, req, resp)
|
||||
}
|
||||
|
||||
// minDHCPMsgSize is the minimum length of the encoded DHCP message in bytes
|
||||
// according to RFC-2131.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc2131#section-2.
|
||||
const minDHCPMsgSize = 576
|
||||
|
||||
// send writes resp for peer to conn considering the req's parameters according
|
||||
// to RFC-2131.
|
||||
//
|
||||
@@ -1001,8 +1132,22 @@ func (s *v4Server) send(peer net.Addr, conn net.PacketConn, req, resp *dhcpv4.DH
|
||||
// Go on since peer is already set to broadcast.
|
||||
}
|
||||
|
||||
log.Debug("dhcpv4: sending to %s: %s", peer, resp.Summary())
|
||||
if _, err := conn.WriteTo(resp.ToBytes(), peer); err != nil {
|
||||
pktData := resp.ToBytes()
|
||||
pktLen := len(pktData)
|
||||
if pktLen < minDHCPMsgSize {
|
||||
// Expand the packet to match the minimum DHCP message length. Although
|
||||
// the dhpcv4 package deals with the BOOTP's lower packet length
|
||||
// constraint, it seems some clients expecting the length being at least
|
||||
// 576 bytes as per RFC 2131 (and an obsolete RFC 1533).
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/4337.
|
||||
pktData = append(pktData, make([]byte, minDHCPMsgSize-pktLen)...)
|
||||
}
|
||||
|
||||
log.Debug("dhcpv4: sending %d bytes to %s: %s", len(pktData), peer, resp.Summary())
|
||||
|
||||
_, err := conn.WriteTo(pktData, peer)
|
||||
if err != nil {
|
||||
log.Error("dhcpv4: conn.Write to %s failed: %s", peer, err)
|
||||
}
|
||||
}
|
||||
@@ -1037,6 +1182,14 @@ func (s *v4Server) Start() (err error) {
|
||||
// No available IP addresses which may appear later.
|
||||
return nil
|
||||
}
|
||||
// Update the value of Domain Name Server option separately from others if
|
||||
// not assigned yet since its value is available only at server's start.
|
||||
//
|
||||
// TODO(e.burkov): Initialize as implicit option with the rest of default
|
||||
// options when it will be possible to do before the call to Start.
|
||||
if !s.explicitOpts.Has(dhcpv4.OptionDomainNameServer) {
|
||||
s.implicitOpts.Update(dhcpv4.OptDNS(dnsIPAddrs...))
|
||||
}
|
||||
|
||||
s.conf.dnsIPAddrs = dnsIPAddrs
|
||||
|
||||
@@ -1162,7 +1315,7 @@ func v4Create(conf V4ServerConf) (srv DHCPServer, err error) {
|
||||
s.conf.leaseTime = time.Second * time.Duration(conf.LeaseDuration)
|
||||
}
|
||||
|
||||
s.options = prepareOptions(s.conf)
|
||||
s.implicitOpts, s.explicitOpts = prepareOptions(s.conf)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
@@ -23,6 +26,7 @@ var (
|
||||
DefaultRangeStart = net.IP{192, 168, 10, 100}
|
||||
DefaultRangeEnd = net.IP{192, 168, 10, 200}
|
||||
DefaultGatewayIP = net.IP{192, 168, 10, 1}
|
||||
DefaultSelfIP = net.IP{192, 168, 10, 2}
|
||||
DefaultSubnetMask = net.IP{255, 255, 255, 0}
|
||||
)
|
||||
|
||||
@@ -39,6 +43,7 @@ func defaultV4ServerConf() (conf V4ServerConf) {
|
||||
GatewayIP: DefaultGatewayIP,
|
||||
SubnetMask: DefaultSubnetMask,
|
||||
notify: notify4,
|
||||
dnsIPAddrs: []net.IP{DefaultSelfIP},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +59,148 @@ func defaultSrv(t *testing.T) (s DHCPServer) {
|
||||
return s
|
||||
}
|
||||
|
||||
func TestV4Server_leasing(t *testing.T) {
|
||||
const (
|
||||
staticName = "static-client"
|
||||
anotherName = "another-client"
|
||||
)
|
||||
|
||||
staticIP := net.IP{192, 168, 10, 10}
|
||||
anotherIP := DefaultRangeStart
|
||||
staticMAC := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
||||
anotherMAC := net.HardwareAddr{0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB}
|
||||
|
||||
s := defaultSrv(t)
|
||||
|
||||
t.Run("add_static", func(t *testing.T) {
|
||||
err := s.AddStaticLease(&Lease{
|
||||
Expiry: time.Unix(leaseExpireStatic, 0),
|
||||
Hostname: staticName,
|
||||
HWAddr: staticMAC,
|
||||
IP: staticIP,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("same_name", func(t *testing.T) {
|
||||
err = s.AddStaticLease(&Lease{
|
||||
Expiry: time.Unix(leaseExpireStatic, 0),
|
||||
Hostname: staticName,
|
||||
HWAddr: anotherMAC,
|
||||
IP: anotherIP,
|
||||
})
|
||||
assert.ErrorIs(t, err, ErrDupHostname)
|
||||
})
|
||||
|
||||
t.Run("same_mac", func(t *testing.T) {
|
||||
wantErrMsg := "dhcpv4: adding static lease: removing " +
|
||||
"dynamic leases for " + anotherIP.String() +
|
||||
" (" + staticMAC.String() + "): static lease already exists"
|
||||
|
||||
err = s.AddStaticLease(&Lease{
|
||||
Expiry: time.Unix(leaseExpireStatic, 0),
|
||||
Hostname: anotherName,
|
||||
HWAddr: staticMAC,
|
||||
IP: anotherIP,
|
||||
})
|
||||
testutil.AssertErrorMsg(t, wantErrMsg, err)
|
||||
})
|
||||
|
||||
t.Run("same_ip", func(t *testing.T) {
|
||||
wantErrMsg := "dhcpv4: adding static lease: removing " +
|
||||
"dynamic leases for " + staticIP.String() +
|
||||
" (" + anotherMAC.String() + "): static lease already exists"
|
||||
|
||||
err = s.AddStaticLease(&Lease{
|
||||
Expiry: time.Unix(leaseExpireStatic, 0),
|
||||
Hostname: anotherName,
|
||||
HWAddr: anotherMAC,
|
||||
IP: staticIP,
|
||||
})
|
||||
testutil.AssertErrorMsg(t, wantErrMsg, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("add_dynamic", func(t *testing.T) {
|
||||
s4, ok := s.(*v4Server)
|
||||
require.True(t, ok)
|
||||
|
||||
discoverAnOffer := func(
|
||||
t *testing.T,
|
||||
name string,
|
||||
ip net.IP,
|
||||
mac net.HardwareAddr,
|
||||
) (resp *dhcpv4.DHCPv4) {
|
||||
testutil.CleanupAndRequireSuccess(t, func() (err error) {
|
||||
return s.ResetLeases(s.GetLeases(LeasesStatic))
|
||||
})
|
||||
|
||||
req, err := dhcpv4.NewDiscovery(
|
||||
mac,
|
||||
dhcpv4.WithOption(dhcpv4.OptHostName(name)),
|
||||
dhcpv4.WithOption(dhcpv4.OptRequestedIPAddress(ip)),
|
||||
dhcpv4.WithOption(dhcpv4.OptClientIdentifier([]byte{1, 2, 3, 4, 5, 6, 8})),
|
||||
dhcpv4.WithGatewayIP(DefaultGatewayIP),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp = &dhcpv4.DHCPv4{}
|
||||
res := s4.handle(req, resp)
|
||||
require.Positive(t, res)
|
||||
require.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType())
|
||||
|
||||
resp.ClientHWAddr = mac
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
t.Run("same_name", func(t *testing.T) {
|
||||
resp := discoverAnOffer(t, staticName, anotherIP, anotherMAC)
|
||||
|
||||
req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption(
|
||||
dhcpv4.OptHostName(staticName),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
|
||||
res := s4.handle(req, resp)
|
||||
require.Positive(t, res)
|
||||
|
||||
assert.Equal(t, aghnet.GenerateHostname(resp.YourIPAddr), resp.HostName())
|
||||
})
|
||||
|
||||
t.Run("same_mac", func(t *testing.T) {
|
||||
resp := discoverAnOffer(t, anotherName, anotherIP, staticMAC)
|
||||
|
||||
req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption(
|
||||
dhcpv4.OptHostName(anotherName),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
|
||||
res := s4.handle(req, resp)
|
||||
require.Positive(t, res)
|
||||
|
||||
fqdnOptData := resp.Options.Get(dhcpv4.OptionFQDN)
|
||||
require.Len(t, fqdnOptData, 3+len(staticName))
|
||||
assert.Equal(t, []uint8(staticName), fqdnOptData[3:])
|
||||
|
||||
assert.Equal(t, staticIP, resp.YourIPAddr)
|
||||
})
|
||||
|
||||
t.Run("same_ip", func(t *testing.T) {
|
||||
resp := discoverAnOffer(t, anotherName, staticIP, anotherMAC)
|
||||
|
||||
req, err := dhcpv4.NewRequestFromOffer(resp, dhcpv4.WithOption(
|
||||
dhcpv4.OptHostName(anotherName),
|
||||
))
|
||||
require.NoError(t, err)
|
||||
|
||||
res := s4.handle(req, resp)
|
||||
require.Positive(t, res)
|
||||
|
||||
assert.NotEqual(t, staticIP, resp.YourIPAddr)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestV4Server_AddRemove_static(t *testing.T) {
|
||||
s := defaultSrv(t)
|
||||
|
||||
@@ -182,7 +329,7 @@ func TestV4_AddReplace(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestV4Server_Process_optionsPriority(t *testing.T) {
|
||||
func TestV4Server_handle_optionsPriority(t *testing.T) {
|
||||
defaultIP := net.IP{192, 168, 1, 1}
|
||||
knownIP := net.IP{1, 2, 3, 4}
|
||||
|
||||
@@ -199,6 +346,8 @@ func TestV4Server_Process_optionsPriority(t *testing.T) {
|
||||
stringutil.WriteToBuilder(b, ",", ip.String())
|
||||
}
|
||||
conf.Options = []string{b.String()}
|
||||
} else {
|
||||
defer func() { s.implicitOpts.Update(dhcpv4.OptDNS(defaultIP)) }()
|
||||
}
|
||||
|
||||
ss, err := v4Create(conf)
|
||||
@@ -228,7 +377,7 @@ func TestV4Server_Process_optionsPriority(t *testing.T) {
|
||||
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
res := s.process(req, resp)
|
||||
res := s.handle(req, resp)
|
||||
require.Equal(t, 1, res)
|
||||
|
||||
o := resp.GetOneOption(dhcpv4.OptionDomainNameServer)
|
||||
@@ -254,6 +403,111 @@ func TestV4Server_Process_optionsPriority(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestV4Server_updateOptions(t *testing.T) {
|
||||
testIP := net.IP{1, 2, 3, 4}
|
||||
|
||||
dontWant := func(c dhcpv4.OptionCode) (opt dhcpv4.Option) {
|
||||
return dhcpv4.OptGeneric(c, nil)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantOpts dhcpv4.Options
|
||||
reqMods []dhcpv4.Modifier
|
||||
confOpts []string
|
||||
}{{
|
||||
name: "requested_default",
|
||||
wantOpts: dhcpv4.OptionsFromList(
|
||||
dhcpv4.OptBroadcastAddress(netutil.IPv4bcast()),
|
||||
),
|
||||
reqMods: []dhcpv4.Modifier{
|
||||
dhcpv4.WithRequestedOptions(dhcpv4.OptionBroadcastAddress),
|
||||
},
|
||||
confOpts: nil,
|
||||
}, {
|
||||
name: "requested_non-default",
|
||||
wantOpts: dhcpv4.OptionsFromList(
|
||||
dhcpv4.OptBroadcastAddress(testIP),
|
||||
),
|
||||
reqMods: []dhcpv4.Modifier{
|
||||
dhcpv4.WithRequestedOptions(dhcpv4.OptionBroadcastAddress),
|
||||
},
|
||||
confOpts: []string{
|
||||
fmt.Sprintf("%d ip %s", dhcpv4.OptionBroadcastAddress, testIP),
|
||||
},
|
||||
}, {
|
||||
name: "non-requested_default",
|
||||
wantOpts: dhcpv4.OptionsFromList(
|
||||
dontWant(dhcpv4.OptionBroadcastAddress),
|
||||
),
|
||||
reqMods: nil,
|
||||
confOpts: nil,
|
||||
}, {
|
||||
name: "non-requested_non-default",
|
||||
wantOpts: dhcpv4.OptionsFromList(
|
||||
dhcpv4.OptBroadcastAddress(testIP),
|
||||
),
|
||||
reqMods: nil,
|
||||
confOpts: []string{
|
||||
fmt.Sprintf("%d ip %s", dhcpv4.OptionBroadcastAddress, testIP),
|
||||
},
|
||||
}, {
|
||||
name: "requested_deleted",
|
||||
wantOpts: dhcpv4.OptionsFromList(
|
||||
dontWant(dhcpv4.OptionBroadcastAddress),
|
||||
),
|
||||
reqMods: []dhcpv4.Modifier{
|
||||
dhcpv4.WithRequestedOptions(dhcpv4.OptionBroadcastAddress),
|
||||
},
|
||||
confOpts: []string{
|
||||
fmt.Sprintf("%d del", dhcpv4.OptionBroadcastAddress),
|
||||
},
|
||||
}, {
|
||||
name: "requested_non-default_deleted",
|
||||
wantOpts: dhcpv4.OptionsFromList(
|
||||
dontWant(dhcpv4.OptionBroadcastAddress),
|
||||
),
|
||||
reqMods: []dhcpv4.Modifier{
|
||||
dhcpv4.WithRequestedOptions(dhcpv4.OptionBroadcastAddress),
|
||||
},
|
||||
confOpts: []string{
|
||||
fmt.Sprintf("%d ip %s", dhcpv4.OptionBroadcastAddress, testIP),
|
||||
fmt.Sprintf("%d del", dhcpv4.OptionBroadcastAddress),
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
req, err := dhcpv4.New(tc.reqMods...)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := dhcpv4.NewReplyFromRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
conf := defaultV4ServerConf()
|
||||
conf.Options = tc.confOpts
|
||||
|
||||
s, err := v4Create(conf)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.IsType(t, (*v4Server)(nil), s)
|
||||
s4, _ := s.(*v4Server)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s4.updateOptions(req, resp)
|
||||
|
||||
for c, v := range tc.wantOpts {
|
||||
if v == nil {
|
||||
assert.NotContains(t, resp.Options, c)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
assert.Equal(t, v, resp.Options.Get(dhcpv4.GenericOptionCode(c)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestV4StaticLease_Get(t *testing.T) {
|
||||
sIface := defaultSrv(t)
|
||||
|
||||
@@ -261,6 +515,7 @@ func TestV4StaticLease_Get(t *testing.T) {
|
||||
require.True(t, ok)
|
||||
|
||||
s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}}
|
||||
s.implicitOpts.Update(dhcpv4.OptDNS(s.conf.dnsIPAddrs...))
|
||||
|
||||
l := &Lease{
|
||||
Hostname: "static-1.local",
|
||||
@@ -282,7 +537,7 @@ func TestV4StaticLease_Get(t *testing.T) {
|
||||
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 1, s.process(req, resp))
|
||||
assert.Equal(t, 1, s.handle(req, resp))
|
||||
})
|
||||
|
||||
// Don't continue if we got any errors in the previous subtest.
|
||||
@@ -305,7 +560,7 @@ func TestV4StaticLease_Get(t *testing.T) {
|
||||
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 1, s.process(req, resp))
|
||||
assert.Equal(t, 1, s.handle(req, resp))
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
@@ -349,6 +604,7 @@ func TestV4DynamicLease_Get(t *testing.T) {
|
||||
require.True(t, ok)
|
||||
|
||||
s.conf.dnsIPAddrs = []net.IP{{192, 168, 10, 1}}
|
||||
s.implicitOpts.Update(dhcpv4.OptDNS(s.conf.dnsIPAddrs...))
|
||||
|
||||
var req, resp *dhcpv4.DHCPv4
|
||||
mac := net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}
|
||||
@@ -363,7 +619,7 @@ func TestV4DynamicLease_Get(t *testing.T) {
|
||||
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 1, s.process(req, resp))
|
||||
assert.Equal(t, 1, s.handle(req, resp))
|
||||
})
|
||||
|
||||
// Don't continue if we got any errors in the previous subtest.
|
||||
@@ -397,7 +653,7 @@ func TestV4DynamicLease_Get(t *testing.T) {
|
||||
resp, err = dhcpv4.NewReplyFromRequest(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 1, s.process(req, resp))
|
||||
assert.Equal(t, 1, s.handle(req, resp))
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
Reference in New Issue
Block a user