diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go index 6043cd95..c1289795 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 true if this option is present, and the +// V6ONLY_WAIT duration if it is. +// +// The IPv6-Only Preferred option is described by RFC 8195, Section 3.1. +func (d *DHCPv4) IPv6OnlyPreferred() (bool, time.Duration) { + v := d.Options.Get(OptionIPv6OnlyPreferred) + if v == nil { + return false, 0 + } + var dur Duration + if err := dur.FromBytes(v); err != nil { + return false, 0 + } + return true, time.Duration(dur) +} + // 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/option_ipv6_only_preferred.go b/dhcpv4/option_ipv6_only_preferred.go new file mode 100644 index 00000000..a5155b10 --- /dev/null +++ b/dhcpv4/option_ipv6_only_preferred.go @@ -0,0 +1,10 @@ +package dhcpv4 + +import ( + "time" +) + +// 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_ipv6_only_preferred_test.go b/dhcpv4/option_ipv6_only_preferred_test.go new file mode 100644 index 00000000..f009f75b --- /dev/null +++ b/dhcpv4/option_ipv6_only_preferred_test.go @@ -0,0 +1,43 @@ +package dhcpv4 + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +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})) + found, v6onlyWait := m.IPv6OnlyPreferred() + require.True(t, found) + require.Equal(t, 43200*time.Second, v6onlyWait) + + // Too short. + m, _ = New(WithGeneric(OptionIPv6OnlyPreferred, []byte{168, 192})) + found, v6onlyWait = m.IPv6OnlyPreferred() + require.False(t, found) + + // Too long. + m, _ = New(WithGeneric(OptionIPv6OnlyPreferred, []byte{1, 1, 1, 1, 1})) + found, v6onlyWait = m.IPv6OnlyPreferred() + require.False(t, found) + + // Empty. + m, _ = New() + require.False(t, found) +} 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",