From fb14ecd046015c4922c683913eace6be63f2d580 Mon Sep 17 00:00:00 2001 From: Brian Candler Date: Sun, 25 Feb 2024 22:24:51 +0700 Subject: [PATCH] Add option IPv6OnlyPreferred (RFC 8925) Signed-off-by: Brian Candler --- dhcpv4/dhcpv4.go | 16 +++++ dhcpv4/modifiers.go | 5 ++ dhcpv4/modifiers_test.go | 9 +++ ...dress_lease_time.go => option_duration.go} | 5 ++ dhcpv4/option_duration_test.go | 71 +++++++++++++++++++ dhcpv4/option_ip_address_lease_time_test.go | 35 --------- dhcpv4/options.go | 2 +- dhcpv4/types.go | 4 ++ 8 files changed, 111 insertions(+), 36 deletions(-) rename dhcpv4/{option_ip_address_lease_time.go => option_duration.go} (85%) create mode 100644 dhcpv4/option_duration_test.go delete mode 100644 dhcpv4/option_ip_address_lease_time_test.go diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go index 6043cd95..b922fb92 100644 --- a/dhcpv4/dhcpv4.go +++ b/dhcpv4/dhcpv4.go @@ -730,6 +730,22 @@ func (d *DHCPv4) IPAddressRebindingTime(def time.Duration) time.Duration { return time.Duration(dur) } +// IPv6OnlyPreferred returns the V6ONLY_WAIT duration, and a boolean +// indicating whether this option was present. +// +// The IPv6-Only Preferred option is described by RFC 8925, Section 3.1. +func (d *DHCPv4) IPv6OnlyPreferred() (time.Duration, bool) { + v := d.Options.Get(OptionIPv6OnlyPreferred) + if v == nil { + return 0, false + } + var dur Duration + if err := dur.FromBytes(v); err != nil { + return 0, false + } + return time.Duration(dur), true +} + // MaxMessageSize returns the DHCP Maximum Message Size if present. // // The Maximum DHCP Message Size option is described by RFC 2132, Section 9.10. diff --git a/dhcpv4/modifiers.go b/dhcpv4/modifiers.go index 68da298c..55863fe0 100644 --- a/dhcpv4/modifiers.go +++ b/dhcpv4/modifiers.go @@ -159,6 +159,11 @@ func WithLeaseTime(leaseTime uint32) Modifier { return WithOption(OptIPAddressLeaseTime(time.Duration(leaseTime) * time.Second)) } +// WithIPv6OnlyPreferred adds or updates an OptIPv6OnlyPreferred +func WithIPv6OnlyPreferred(v6OnlyWait uint32) Modifier { + return WithOption(OptIPv6OnlyPreferred(time.Duration(v6OnlyWait) * time.Second)) +} + // WithDomainSearchList adds or updates an OptionDomainSearch func WithDomainSearchList(searchList ...string) Modifier { return WithOption(OptDomainSearch(&rfc1035label.Labels{ diff --git a/dhcpv4/modifiers_test.go b/dhcpv4/modifiers_test.go index 57d8ff04..7659fc2a 100644 --- a/dhcpv4/modifiers_test.go +++ b/dhcpv4/modifiers_test.go @@ -154,6 +154,15 @@ func TestWithDNS(t *testing.T) { require.Equal(t, net.ParseIP("10.0.0.2").To4(), dns[1]) } +func TestWithIPv6OnlyPreferred(t *testing.T) { + d, err := New(WithIPv6OnlyPreferred(300)) + require.NoError(t, err) + + v6pref, ok := d.IPv6OnlyPreferred() + require.True(t, ok) + require.Equal(t, 300*time.Second, v6pref) +} + func TestWithDomainSearchList(t *testing.T) { d, err := New(WithDomainSearchList("slackware.it", "dhcp.slackware.it")) require.NoError(t, err) diff --git a/dhcpv4/option_ip_address_lease_time.go b/dhcpv4/option_duration.go similarity index 85% rename from dhcpv4/option_ip_address_lease_time.go rename to dhcpv4/option_duration.go index 0b5cd30c..bfa01366 100644 --- a/dhcpv4/option_ip_address_lease_time.go +++ b/dhcpv4/option_duration.go @@ -39,3 +39,8 @@ func (d Duration) String() string { func OptIPAddressLeaseTime(d time.Duration) Option { return Option{Code: OptionIPAddressLeaseTime, Value: Duration(d)} } + +// The IPv6-Only Preferred option is described by RFC 8925, Section 3.1 +func OptIPv6OnlyPreferred(d time.Duration) Option { + return Option{Code: OptionIPv6OnlyPreferred, Value: Duration(d)} +} diff --git a/dhcpv4/option_duration_test.go b/dhcpv4/option_duration_test.go new file mode 100644 index 00000000..1b3a0e50 --- /dev/null +++ b/dhcpv4/option_duration_test.go @@ -0,0 +1,71 @@ +package dhcpv4 + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestOptIPAddressLeaseTime(t *testing.T) { + o := OptIPAddressLeaseTime(43200 * time.Second) + require.Equal(t, OptionIPAddressLeaseTime, o.Code, "Code") + require.Equal(t, []byte{0, 0, 168, 192}, o.Value.ToBytes(), "ToBytes") + require.Equal(t, "IP Addresses Lease Time: 12h0m0s", o.String(), "String") +} + +func TestGetIPAddressLeaseTime(t *testing.T) { + m, _ := New(WithGeneric(OptionIPAddressLeaseTime, []byte{0, 0, 168, 192})) + leaseTime := m.IPAddressLeaseTime(0) + require.Equal(t, 43200*time.Second, leaseTime) + + // Too short. + m, _ = New(WithGeneric(OptionIPAddressLeaseTime, []byte{168, 192})) + leaseTime = m.IPAddressLeaseTime(0) + require.Equal(t, time.Duration(0), leaseTime) + + // Too long. + m, _ = New(WithGeneric(OptionIPAddressLeaseTime, []byte{1, 1, 1, 1, 1})) + leaseTime = m.IPAddressLeaseTime(0) + require.Equal(t, time.Duration(0), leaseTime) + + // Empty. + m, _ = New() + require.Equal(t, time.Duration(10), m.IPAddressLeaseTime(10)) +} + +func TestOptIPv6OnlyPreferred(t *testing.T) { + o := OptIPv6OnlyPreferred(43200 * time.Second) + require.Equal(t, OptionIPv6OnlyPreferred, o.Code, "Code") + require.Equal(t, []byte{0, 0, 168, 192}, o.Value.ToBytes(), "ToBytes") + require.Equal(t, "IPv6-Only Preferred: 12h0m0s", o.String(), "String") +} + +func TestOptIPv6OnlyPreferredZero(t *testing.T) { + o := OptIPv6OnlyPreferred(0) + require.Equal(t, OptionIPv6OnlyPreferred, o.Code, "Code") + require.Equal(t, []byte{0, 0, 0, 0}, o.Value.ToBytes(), "ToBytes") + require.Equal(t, "IPv6-Only Preferred: 0s", o.String(), "String") +} + +func TestGetIPv6OnlyPreferred(t *testing.T) { + m, _ := New(WithGeneric(OptionIPv6OnlyPreferred, []byte{0, 0, 168, 192})) + v6onlyWait, ok := m.IPv6OnlyPreferred() + require.True(t, ok) + require.Equal(t, 43200*time.Second, v6onlyWait) + + // Too short. + m, _ = New(WithGeneric(OptionIPv6OnlyPreferred, []byte{168, 192})) + _, ok = m.IPv6OnlyPreferred() + require.False(t, ok) + + // Too long. + m, _ = New(WithGeneric(OptionIPv6OnlyPreferred, []byte{1, 1, 1, 1, 1})) + _, ok = m.IPv6OnlyPreferred() + require.False(t, ok) + + // Missing. + m, _ = New() + _, ok = m.IPv6OnlyPreferred() + require.False(t, ok) +} diff --git a/dhcpv4/option_ip_address_lease_time_test.go b/dhcpv4/option_ip_address_lease_time_test.go deleted file mode 100644 index edd709f3..00000000 --- a/dhcpv4/option_ip_address_lease_time_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package dhcpv4 - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestOptIPAddressLeaseTime(t *testing.T) { - o := OptIPAddressLeaseTime(43200 * time.Second) - require.Equal(t, OptionIPAddressLeaseTime, o.Code, "Code") - require.Equal(t, []byte{0, 0, 168, 192}, o.Value.ToBytes(), "ToBytes") - require.Equal(t, "IP Addresses Lease Time: 12h0m0s", o.String(), "String") -} - -func TestGetIPAddressLeaseTime(t *testing.T) { - m, _ := New(WithGeneric(OptionIPAddressLeaseTime, []byte{0, 0, 168, 192})) - leaseTime := m.IPAddressLeaseTime(0) - require.Equal(t, 43200*time.Second, leaseTime) - - // Too short. - m, _ = New(WithGeneric(OptionIPAddressLeaseTime, []byte{168, 192})) - leaseTime = m.IPAddressLeaseTime(0) - require.Equal(t, time.Duration(0), leaseTime) - - // Too long. - m, _ = New(WithGeneric(OptionIPAddressLeaseTime, []byte{1, 1, 1, 1, 1})) - leaseTime = m.IPAddressLeaseTime(0) - require.Equal(t, time.Duration(0), leaseTime) - - // Empty. - m, _ = New() - require.Equal(t, time.Duration(10), m.IPAddressLeaseTime(10)) -} diff --git a/dhcpv4/options.go b/dhcpv4/options.go index 9d404b43..1b56c28d 100644 --- a/dhcpv4/options.go +++ b/dhcpv4/options.go @@ -336,7 +336,7 @@ func getOption(code OptionCode, data []byte, vendorDecoder OptionDecoder) fmt.St case OptionDNSDomainSearchList: d = &rfc1035label.Labels{} - case OptionIPAddressLeaseTime: + case OptionIPAddressLeaseTime, OptionIPv6OnlyPreferred: var dur Duration d = &dur diff --git a/dhcpv4/types.go b/dhcpv4/types.go index 7dddefd8..80ea49cf 100644 --- a/dhcpv4/types.go +++ b/dhcpv4/types.go @@ -238,6 +238,8 @@ const ( OptionGeoConfCivic optionCode = 99 OptionIEEE10031TZString optionCode = 100 OptionReferenceToTZDatabase optionCode = 101 + // Option 108 returned in RFC 8925 + OptionIPv6OnlyPreferred optionCode = 108 // Options 102-111 returned in RFC 3679 OptionNetInfoParentServerAddress optionCode = 112 OptionNetInfoParentServerTag optionCode = 113 @@ -401,6 +403,8 @@ var optionCodeToString = map[OptionCode]string{ OptionGeoConfCivic: "GEOCONF_CIVIC", OptionIEEE10031TZString: "IEEE 1003.1 TZ String", OptionReferenceToTZDatabase: "Reference to the TZ Database", + // Option 108 returned in RFC 8925 + OptionIPv6OnlyPreferred: "IPv6-Only Preferred", // Options 102-111 returned in RFC 3679 OptionNetInfoParentServerAddress: "NetInfo Parent Server Address", OptionNetInfoParentServerTag: "NetInfo Parent Server Tag",