mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-09-11 10:45:42 +03:00
Pull request 2096: 4923 gopacket dhcp vol.5
Updates #4923. Squashed commit of the following: commit 762a3f9b7d7d4dd8799e8bf4df632b8d50321bf8 Merge: 2af65b42a34a34dc05
Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Dec 12 11:13:21 2023 +0300 Merge branch 'master' into 4923-gopacket-dhcp-vol.5 commit 2af65b42a62b92ec5bc28ef81eb6f08d0f9f443e Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Fri Dec 8 16:22:51 2023 +0300 dhcpsvc: imp code commit 71233b9952b0d74e7e890d6755652877bc4c543b Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Fri Dec 8 15:26:25 2023 +0300 dhcpsvc: imp docs commit 2949544f32b52d592f76e53062cf017e08073d29 Merge: 593e9edaa214175eb4
Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Dec 7 16:48:18 2023 +0300 Merge branch 'master' into 4923-gopacket-dhcp-vol.5 commit 593e9edaa9e776db35b3bedfa942c015d7b1e4af Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Dec 7 16:45:10 2023 +0300 dhcpsvc: imp docs commit cdb1915c7db3bf36800b40bc6aedc0e20f55f899 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Dec 7 16:33:56 2023 +0300 dhcpsvc: imp code commit a0c423c2b39d674debf4c0fedf6208f656be861a Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Dec 5 20:56:43 2023 +0300 dnspsvc: add opts commit 050ab7f9410c206287c66376e21d36af8a3da384 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Dec 5 20:17:37 2023 +0300 WIP
This commit is contained in:
parent
34a34dc05a
commit
79d7a1ef46
@ -6,7 +6,10 @@ import (
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"github.com/google/gopacket/layers"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// IPv4Config is the interface-specific configuration for DHCPv4.
|
||||
@ -26,7 +29,9 @@ type IPv4Config struct {
|
||||
// RangeEnd is the last address in the range to assign to DHCP clients.
|
||||
RangeEnd netip.Addr
|
||||
|
||||
// Options is the list of DHCP options to send to DHCP clients.
|
||||
// Options is the list of DHCP options to send to DHCP clients. The options
|
||||
// having a zero value within the Length field are treated as deletions of
|
||||
// the corresponding options, either implicit or explicit.
|
||||
Options layers.DHCPOptions
|
||||
|
||||
// LeaseDuration is the TTL of a DHCP lease.
|
||||
@ -73,7 +78,14 @@ type iface4 struct {
|
||||
// name is the name of the interface.
|
||||
name string
|
||||
|
||||
// TODO(e.burkov): Add options.
|
||||
// implicitOpts are the options listed in Appendix A of RFC 2131 and
|
||||
// initialized with default values. It must not have intersections with
|
||||
// explicitOpts.
|
||||
implicitOpts layers.DHCPOptions
|
||||
|
||||
// explicitOpts are the user-configured options. It must not have
|
||||
// intersections with implicitOpts.
|
||||
explicitOpts layers.DHCPOptions
|
||||
|
||||
// leaseTTL is the time-to-live of dynamic leases on this interface.
|
||||
leaseTTL time.Duration
|
||||
@ -103,11 +115,206 @@ func newIface4(name string, conf *IPv4Config) (i *iface4, err error) {
|
||||
return nil, fmt.Errorf("gateway ip %s in the ip range %s", conf.GatewayIP, addrSpace)
|
||||
}
|
||||
|
||||
return &iface4{
|
||||
i = &iface4{
|
||||
name: name,
|
||||
gateway: conf.GatewayIP,
|
||||
subnet: subnet,
|
||||
addrSpace: addrSpace,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
}, nil
|
||||
}
|
||||
i.implicitOpts, i.explicitOpts = conf.options()
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// options returns the implicit and explicit options for the interface. The two
|
||||
// lists are disjoint and the implicit options are initialized with default
|
||||
// values.
|
||||
//
|
||||
// TODO(e.burkov): DRY with the IPv6 version.
|
||||
func (conf *IPv4Config) options() (implicit, explicit layers.DHCPOptions) {
|
||||
// Set default values of host configuration parameters listed in Appendix A
|
||||
// of RFC-2131.
|
||||
implicit = layers.DHCPOptions{
|
||||
// Values From Configuration
|
||||
|
||||
layers.NewDHCPOption(layers.DHCPOptSubnetMask, conf.SubnetMask.AsSlice()),
|
||||
layers.NewDHCPOption(layers.DHCPOptRouter, conf.GatewayIP.AsSlice()),
|
||||
|
||||
// IP-Layer Per Host
|
||||
|
||||
// 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.
|
||||
layers.NewDHCPOption(layers.DHCPOptIPForwarding, []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.
|
||||
layers.NewDHCPOption(layers.DHCPOptSourceRouting, []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.
|
||||
layers.NewDHCPOption(layers.DHCPOptDatagramMTU, []byte{0x2, 0x40}),
|
||||
|
||||
// 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.
|
||||
layers.NewDHCPOption(layers.DHCPOptDefaultTTL, []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.
|
||||
layers.NewDHCPOption(layers.DHCPOptPathMTUAgingTimeout, []byte{0x0, 0x0, 0x2, 0x58}),
|
||||
|
||||
// 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.
|
||||
layers.NewDHCPOption(layers.DHCPOptPathPlateuTableOption, []byte{
|
||||
0x0, 0x44,
|
||||
0x1, 0x28,
|
||||
0x1, 0xFC,
|
||||
0x3, 0xEE,
|
||||
0x5, 0xD4,
|
||||
0x7, 0xD2,
|
||||
0x11, 0x0,
|
||||
0x1F, 0xE6,
|
||||
0x45, 0xFA,
|
||||
}),
|
||||
|
||||
// IP-Layer Per Interface
|
||||
|
||||
// Don't set the Interface MTU because client may choose the value on
|
||||
// their own since it's listed in the [Host Requirements RFC]. It also
|
||||
// seems the values listed there sometimes appear obsolete, see
|
||||
// https://github.com/AdguardTeam/AdGuardHome/issues/5281.
|
||||
//
|
||||
// [Host Requirements RFC]: https://datatracker.ietf.org/doc/html/rfc1122#section-3.3.3.
|
||||
|
||||
// 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.
|
||||
layers.NewDHCPOption(layers.DHCPOptAllSubsLocal, []byte{0x0}),
|
||||
|
||||
// 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.
|
||||
layers.NewDHCPOption(layers.DHCPOptMaskDiscovery, []byte{0x0}),
|
||||
|
||||
// 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.
|
||||
layers.NewDHCPOption(layers.DHCPOptMaskSupplier, []byte{0x0}),
|
||||
|
||||
// Set the Perform Router Discovery Option to true as per Router
|
||||
// Discovery Document.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1256#section-5.1.
|
||||
layers.NewDHCPOption(layers.DHCPOptRouterDiscovery, []byte{0x1}),
|
||||
|
||||
// The all-routers address is preferred wherever possible.
|
||||
//
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1256#section-5.1.
|
||||
layers.NewDHCPOption(layers.DHCPOptSolicitAddr, 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.
|
||||
layers.NewDHCPOption(layers.DHCPOptBroadcastAddr, netutil.IPv4bcast()),
|
||||
|
||||
// Link-Layer Per Interface
|
||||
|
||||
// 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.
|
||||
layers.NewDHCPOption(layers.DHCPOptARPTrailers, []byte{0x0}),
|
||||
|
||||
// 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.
|
||||
layers.NewDHCPOption(layers.DHCPOptARPTimeout, []byte{0x0, 0x0, 0x0, 0x3C}),
|
||||
|
||||
// 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.
|
||||
layers.NewDHCPOption(layers.DHCPOptEthernetEncap, []byte{0x0}),
|
||||
|
||||
// TCP Per Host
|
||||
|
||||
// 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.
|
||||
layers.NewDHCPOption(layers.DHCPOptTCPTTL, []byte{0x0, 0x0, 0x0, 0x3C}),
|
||||
|
||||
// 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.
|
||||
layers.NewDHCPOption(layers.DHCPOptTCPKeepAliveInt, []byte{0x0, 0x0, 0x1C, 0x20}),
|
||||
|
||||
// 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.
|
||||
layers.NewDHCPOption(layers.DHCPOptTCPKeepAliveGarbage, []byte{0x1}),
|
||||
}
|
||||
slices.SortFunc(implicit, compareV4OptionCodes)
|
||||
|
||||
// Set values for explicitly configured options.
|
||||
for _, exp := range conf.Options {
|
||||
i, found := slices.BinarySearchFunc(implicit, exp, compareV4OptionCodes)
|
||||
if found {
|
||||
implicit = slices.Delete(implicit, i, i+1)
|
||||
}
|
||||
|
||||
i, found = slices.BinarySearchFunc(explicit, exp, compareV4OptionCodes)
|
||||
if exp.Length > 0 {
|
||||
explicit = slices.Insert(explicit, i, exp)
|
||||
} else if found {
|
||||
explicit = slices.Delete(explicit, i, i+1)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("dhcpsvc: v4: implicit options: %s", implicit)
|
||||
log.Debug("dhcpsvc: v4: explicit options: %s", explicit)
|
||||
|
||||
return implicit, explicit
|
||||
}
|
||||
|
||||
// compareV4OptionCodes compares option codes of a and b.
|
||||
func compareV4OptionCodes(a, b layers.DHCPOption) (res int) {
|
||||
return int(a.Type) - int(b.Type)
|
||||
}
|
||||
|
88
internal/dhcpsvc/v4_internal_test.go
Normal file
88
internal/dhcpsvc/v4_internal_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
package dhcpsvc
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIPv4Config_Options(t *testing.T) {
|
||||
oneIP, otherIP := netip.MustParseAddr("1.2.3.4"), netip.MustParseAddr("5.6.7.8")
|
||||
subnetMask := netip.MustParseAddr("255.255.0.0")
|
||||
|
||||
opt1 := layers.NewDHCPOption(layers.DHCPOptSubnetMask, subnetMask.AsSlice())
|
||||
opt6 := layers.NewDHCPOption(layers.DHCPOptDNS, append(oneIP.AsSlice(), otherIP.AsSlice()...))
|
||||
opt28 := layers.NewDHCPOption(layers.DHCPOptBroadcastAddr, oneIP.AsSlice())
|
||||
opt121 := layers.NewDHCPOption(layers.DHCPOptClasslessStaticRoute, []byte("cba"))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
conf *IPv4Config
|
||||
wantExplicit layers.DHCPOptions
|
||||
}{{
|
||||
name: "all_default",
|
||||
conf: &IPv4Config{
|
||||
Options: nil,
|
||||
},
|
||||
wantExplicit: nil,
|
||||
}, {
|
||||
name: "configured_ip",
|
||||
conf: &IPv4Config{
|
||||
Options: layers.DHCPOptions{opt28},
|
||||
},
|
||||
wantExplicit: layers.DHCPOptions{opt28},
|
||||
}, {
|
||||
name: "configured_ips",
|
||||
conf: &IPv4Config{
|
||||
Options: layers.DHCPOptions{opt6},
|
||||
},
|
||||
wantExplicit: layers.DHCPOptions{opt6},
|
||||
}, {
|
||||
name: "configured_del",
|
||||
conf: &IPv4Config{
|
||||
Options: layers.DHCPOptions{
|
||||
layers.NewDHCPOption(layers.DHCPOptBroadcastAddr, nil),
|
||||
},
|
||||
},
|
||||
wantExplicit: nil,
|
||||
}, {
|
||||
name: "rewritten_del",
|
||||
conf: &IPv4Config{
|
||||
Options: layers.DHCPOptions{
|
||||
layers.NewDHCPOption(layers.DHCPOptBroadcastAddr, nil),
|
||||
opt28,
|
||||
},
|
||||
},
|
||||
wantExplicit: layers.DHCPOptions{opt28},
|
||||
}, {
|
||||
name: "configured_and_del",
|
||||
conf: &IPv4Config{
|
||||
Options: layers.DHCPOptions{
|
||||
layers.NewDHCPOption(layers.DHCPOptClasslessStaticRoute, []byte("a")),
|
||||
layers.NewDHCPOption(layers.DHCPOptClasslessStaticRoute, nil),
|
||||
opt121,
|
||||
},
|
||||
},
|
||||
wantExplicit: layers.DHCPOptions{opt121},
|
||||
}, {
|
||||
name: "replace_config_value",
|
||||
conf: &IPv4Config{
|
||||
SubnetMask: netip.MustParseAddr("255.255.255.0"),
|
||||
Options: layers.DHCPOptions{opt1},
|
||||
},
|
||||
wantExplicit: layers.DHCPOptions{opt1},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
imp, exp := tc.conf.options()
|
||||
assert.Equal(t, tc.wantExplicit, exp)
|
||||
|
||||
for c := range exp {
|
||||
assert.NotContains(t, imp, c)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -5,7 +5,9 @@ import (
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/google/gopacket/layers"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// IPv6Config is the interface-specific configuration for DHCPv6.
|
||||
@ -13,8 +15,10 @@ type IPv6Config struct {
|
||||
// RangeStart is the first address in the range to assign to DHCP clients.
|
||||
RangeStart netip.Addr
|
||||
|
||||
// Options is the list of DHCP options to send to DHCP clients.
|
||||
Options layers.DHCPOptions
|
||||
// Options is the list of DHCP options to send to DHCP clients. The options
|
||||
// with zero length are treated as deletions of the corresponding options,
|
||||
// either implicit or explicit.
|
||||
Options layers.DHCPv6Options
|
||||
|
||||
// LeaseDuration is the TTL of a DHCP lease.
|
||||
LeaseDuration time.Duration
|
||||
@ -58,6 +62,15 @@ type iface6 struct {
|
||||
// name is the name of the interface.
|
||||
name string
|
||||
|
||||
// implicitOpts are the DHCPv6 options listed in RFC 8415 (and others) and
|
||||
// initialized with default values. It must not have intersections with
|
||||
// explicitOpts.
|
||||
implicitOpts layers.DHCPv6Options
|
||||
|
||||
// explicitOpts are the user-configured options. It must not have
|
||||
// intersections with implicitOpts.
|
||||
explicitOpts layers.DHCPv6Options
|
||||
|
||||
// leaseTTL is the time-to-live of dynamic leases on this interface.
|
||||
leaseTTL time.Duration
|
||||
|
||||
@ -78,11 +91,45 @@ func newIface6(name string, conf *IPv6Config) (i *iface6) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &iface6{
|
||||
i = &iface6{
|
||||
name: name,
|
||||
rangeStart: conf.RangeStart,
|
||||
leaseTTL: conf.LeaseDuration,
|
||||
raSLAACOnly: conf.RASLAACOnly,
|
||||
raAllowSLAAC: conf.RAAllowSLAAC,
|
||||
}
|
||||
i.implicitOpts, i.explicitOpts = conf.options()
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// options returns the implicit and explicit options for the interface. The two
|
||||
// lists are disjoint and the implicit options are initialized with default
|
||||
// values.
|
||||
//
|
||||
// TODO(e.burkov): Add implicit options according to RFC.
|
||||
func (conf *IPv6Config) options() (implicit, explicit layers.DHCPv6Options) {
|
||||
// Set default values of host configuration parameters listed in RFC 8415.
|
||||
implicit = layers.DHCPv6Options{}
|
||||
slices.SortFunc(implicit, compareV6OptionCodes)
|
||||
|
||||
// Set values for explicitly configured options.
|
||||
for _, exp := range conf.Options {
|
||||
i, found := slices.BinarySearchFunc(implicit, exp, compareV6OptionCodes)
|
||||
if found {
|
||||
implicit = slices.Delete(implicit, i, i+1)
|
||||
}
|
||||
|
||||
explicit = append(explicit, exp)
|
||||
}
|
||||
|
||||
log.Debug("dhcpsvc: v6: implicit options: %s", implicit)
|
||||
log.Debug("dhcpsvc: v6: explicit options: %s", explicit)
|
||||
|
||||
return implicit, explicit
|
||||
}
|
||||
|
||||
// compareV6OptionCodes compares option codes of a and b.
|
||||
func compareV6OptionCodes(a, b layers.DHCPv6Option) (res int) {
|
||||
return int(a.Code) - int(b.Code)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user