diff options
-rw-r--r-- | dhcpv6/modifiers.go | 24 | ||||
-rw-r--r-- | dhcpv6/modifiers_test.go | 6 | ||||
-rw-r--r-- | dhcpv6/option_iapd.go | 37 | ||||
-rw-r--r-- | dhcpv6/option_iapd_test.go | 27 | ||||
-rw-r--r-- | dhcpv6/option_iaprefix.go | 86 | ||||
-rw-r--r-- | dhcpv6/option_iaprefix_test.go | 39 |
6 files changed, 133 insertions, 86 deletions
diff --git a/dhcpv6/modifiers.go b/dhcpv6/modifiers.go index 95f49d1..96fbf4b 100644 --- a/dhcpv6/modifiers.go +++ b/dhcpv6/modifiers.go @@ -33,7 +33,7 @@ func WithNetboot(d DHCPv6) { func WithFQDN(flags uint8, domainname string) Modifier { return func(d DHCPv6) { d.UpdateOption(&OptFQDN{ - Flags: flags, + Flags: flags, DomainName: &rfc1035label.Labels{ Labels: []string{domainname}, }, @@ -136,19 +136,19 @@ func WithDHCP4oDHCP6Server(addrs ...net.IP) Modifier { // WithIAPD adds or updates an IAPD option with the provided IAID and // prefix options to a DHCPv6 packet. -func WithIAPD(iaid [4]byte, prefixes ...OptIAPrefix) Modifier { +func WithIAPD(iaid [4]byte, prefixes ...*OptIAPrefix) Modifier { return func(d DHCPv6) { - opt := d.GetOneOption(OptionIAPD) - if opt == nil { - opt = &OptIAForPrefixDelegation{} - } - iaPd := opt.(*OptIAForPrefixDelegation) - - copy(iaPd.IaId[:], iaid[:]) + if msg, ok := d.(*Message); ok { + opt := msg.Options.OneIAPD() + if opt == nil { + opt = &OptIAPD{} + } + copy(opt.IaId[:], iaid[:]) - for _, prefix := range prefixes { - iaPd.Options.Add(&prefix) + for _, prefix := range prefixes { + opt.Options.Add(prefix) + } + d.UpdateOption(opt) } - d.UpdateOption(iaPd) } } diff --git a/dhcpv6/modifiers_test.go b/dhcpv6/modifiers_test.go index 8d06e75..2354bd0 100644 --- a/dhcpv6/modifiers_test.go +++ b/dhcpv6/modifiers_test.go @@ -110,12 +110,12 @@ func TestWithDHCP4oDHCP6Server(t *testing.T) { func TestWithIAPD(t *testing.T) { var d Message - prefix := OptIAPrefix{ + _, pre, _ := net.ParseCIDR("2001:DB8:7689::/48") + prefix := &OptIAPrefix{ PreferredLifetime: 3600, ValidLifetime: 5200, + Prefix: pre, } - prefix.SetPrefixLength(48) - prefix.SetIPv6Prefix(net.ParseIP("2001:DB8:7689::")) WithIAPD([4]byte{1, 2, 3, 4}, prefix)(&d) opt := d.Options.IAPD() require.Equal(t, 1, len(opt)) diff --git a/dhcpv6/option_iapd.go b/dhcpv6/option_iapd.go index 03f3744..496aa81 100644 --- a/dhcpv6/option_iapd.go +++ b/dhcpv6/option_iapd.go @@ -7,13 +7,46 @@ import ( "github.com/u-root/u-root/pkg/uio" ) +// PDOptions are options used with the IAPD (prefix delegation) option. +// +// RFC 3633 describes that IA_PD-options may contain the IAPrefix option and +// the StatusCode option. +type PDOptions struct { + Options +} + +// Prefixes are the prefixes associated with this delegation. +func (po PDOptions) Prefixes() []*OptIAPrefix { + opts := po.Options.Get(OptionIAPrefix) + pre := make([]*OptIAPrefix, 0, len(opts)) + for _, o := range opts { + if iap, ok := o.(*OptIAPrefix); ok { + pre = append(pre, iap) + } + } + return pre +} + +// Status returns the status code associated with this option. +func (po PDOptions) Status() *OptStatusCode { + opt := po.Options.GetOne(OptionStatusCode) + if opt == nil { + return nil + } + sc, ok := opt.(*OptStatusCode) + if !ok { + return nil + } + return sc +} + // OptIAPD implements the identity association for prefix // delegation option defined by RFC 3633, Section 9. type OptIAPD struct { IaId [4]byte T1 time.Duration T2 time.Duration - Options Options + Options PDOptions } // Code returns the option code @@ -37,7 +70,7 @@ func (op *OptIAPD) ToBytes() []byte { // String returns a string representation of the OptIAPD data func (op *OptIAPD) String() string { - return fmt.Sprintf("OptIAPD{IAID=%v, t1=%v, t2=%v, options=%v}", + return fmt.Sprintf("IAPD: {IAID=%v, t1=%v, t2=%v, Options=[%v]}", op.IaId, op.T1, op.T2, op.Options) } diff --git a/dhcpv6/option_iapd_test.go b/dhcpv6/option_iapd_test.go index babb5a7..b2b9d3f 100644 --- a/dhcpv6/option_iapd_test.go +++ b/dhcpv6/option_iapd_test.go @@ -53,17 +53,20 @@ func TestOptIAPDParseOptIAPDInvalidOptions(t *testing.T) { } func TestOptIAPDToBytes(t *testing.T) { - oaddr := OptIAPrefix{} - oaddr.PreferredLifetime = 0xaabbccdd * time.Second - oaddr.ValidLifetime = 0xeeff0011 * time.Second - oaddr.SetPrefixLength(36) - oaddr.SetIPv6Prefix(net.IPv6loopback) - - opt := OptIAPD{} - opt.IaId = [4]byte{1, 2, 3, 4} - opt.T1 = 12345 * time.Second - opt.T2 = 54321 * time.Second - opt.Options = append(opt.Options, &oaddr) + oaddr := OptIAPrefix{ + PreferredLifetime: 0xaabbccdd * time.Second, + ValidLifetime: 0xeeff0011 * time.Second, + Prefix: &net.IPNet{ + Mask: net.CIDRMask(36, 128), + IP: net.IPv6loopback, + }, + } + opt := OptIAPD{ + IaId: [4]byte{1, 2, 3, 4}, + T1: 12345 * time.Second, + T2: 54321 * time.Second, + Options: PDOptions{[]Option{&oaddr}}, + } expected := []byte{ 1, 2, 3, 4, // IA ID @@ -105,7 +108,7 @@ func TestOptIAPDString(t *testing.T) { ) require.Contains( t, str, - "options=[", + "Options=[", "String() should return a list of options", ) } diff --git a/dhcpv6/option_iaprefix.go b/dhcpv6/option_iaprefix.go index bb907b2..750f0cd 100644 --- a/dhcpv6/option_iaprefix.go +++ b/dhcpv6/option_iaprefix.go @@ -8,6 +8,30 @@ import ( "github.com/u-root/u-root/pkg/uio" ) +// PrefixOptions are the options valid for use with IAPrefix option field. +// +// RFC 3633 states that it's just the StatusCode option. +// +// RFC 8415 Appendix C does not list the Status Code option as valid, but it +// does say that the previous text in RFC 8415 Section 21.22 supersedes that +// table. Section 21.22 does mention the Status Code option. +type PrefixOptions struct { + Options +} + +// Status returns the status code associated with this option. +func (po PrefixOptions) Status() *OptStatusCode { + opt := po.Options.GetOne(OptionStatusCode) + if opt == nil { + return nil + } + sc, ok := opt.(*OptStatusCode) + if !ok { + return nil + } + return sc +} + // OptIAPrefix implements the IAPrefix option. // // This module defines the OptIAPrefix structure. @@ -15,9 +39,8 @@ import ( type OptIAPrefix struct { PreferredLifetime time.Duration ValidLifetime time.Duration - prefixLength byte - ipv6Prefix net.IP - Options Options + Prefix *net.IPNet + Options PrefixOptions } func (op *OptIAPrefix) Code() OptionCode { @@ -33,44 +56,22 @@ func (op *OptIAPrefix) ToBytes() []byte { t2 := Duration{op.ValidLifetime} t2.Marshal(buf) - buf.Write8(op.prefixLength) - write16(buf, op.ipv6Prefix) + if op.Prefix != nil { + // Even if Mask is nil, Size will return 0 without panicking. + length, _ := op.Prefix.Mask.Size() + buf.Write8(uint8(length)) + write16(buf, op.Prefix.IP) + } else { + buf.Write8(0) + write16(buf, nil) + } buf.WriteBytes(op.Options.ToBytes()) return buf.Data() } -func (op *OptIAPrefix) PrefixLength() byte { - return op.prefixLength -} - -func (op *OptIAPrefix) SetPrefixLength(pl byte) { - op.prefixLength = pl -} - -// IPv6Prefix returns the ipv6Prefix -func (op *OptIAPrefix) IPv6Prefix() net.IP { - return op.ipv6Prefix -} - -// SetIPv6Prefix sets the ipv6Prefix -func (op *OptIAPrefix) SetIPv6Prefix(p net.IP) { - op.ipv6Prefix = p -} - func (op *OptIAPrefix) String() string { - return fmt.Sprintf("OptIAPrefix{preferredlifetime=%v, validlifetime=%v, prefixlength=%v, ipv6prefix=%v, options=%v}", - op.PreferredLifetime, op.ValidLifetime, op.PrefixLength(), op.IPv6Prefix(), op.Options) -} - -// GetOneOption will get an option of the give type from the Options field, if -// it is present. It will return `nil` otherwise -func (op *OptIAPrefix) GetOneOption(code OptionCode) Option { - return op.Options.GetOne(code) -} - -// DelOption will remove all the options that match a Option code. -func (op *OptIAPrefix) DelOption(code OptionCode) { - op.Options.Del(code) + return fmt.Sprintf("IAPrefix: {PreferredLifetime=%v, ValidLifetime=%v, Prefix=%s, Options=%v}", + op.PreferredLifetime, op.ValidLifetime, op.Prefix, op.Options) } // ParseOptIAPrefix an OptIAPrefix structure from a sequence of bytes. The @@ -85,8 +86,17 @@ func ParseOptIAPrefix(data []byte) (*OptIAPrefix, error) { opt.PreferredLifetime = t1.Duration opt.ValidLifetime = t2.Duration - opt.prefixLength = buf.Read8() - opt.ipv6Prefix = net.IP(buf.CopyN(net.IPv6len)) + length := buf.Read8() + ip := net.IP(buf.CopyN(net.IPv6len)) + + if length == 0 { + opt.Prefix = nil + } else { + opt.Prefix = &net.IPNet{ + Mask: net.CIDRMask(int(length), 128), + IP: ip, + } + } if err := opt.Options.FromBytes(buf.ReadAll()); err != nil { return nil, err } diff --git a/dhcpv6/option_iaprefix_test.go b/dhcpv6/option_iaprefix_test.go index baa4808..27d0c95 100644 --- a/dhcpv6/option_iaprefix_test.go +++ b/dhcpv6/option_iaprefix_test.go @@ -3,6 +3,7 @@ package dhcpv6 import ( "bytes" "net" + "reflect" "testing" "time" @@ -15,23 +16,22 @@ func TestOptIAPrefix(t *testing.T) { 0xee, 0xff, 0x00, 0x11, // validLifetime 36, // prefixLength 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // ipv6Prefix - 0, 8, 0, 2, 0xaa, 0xbb, // options } opt, err := ParseOptIAPrefix(buf) if err != nil { t.Fatal(err) } - if pl := opt.PreferredLifetime; pl != 0xaabbccdd*time.Second { - t.Fatalf("Invalid Preferred Lifetime. Expected 0xaabbccdd, got %v", pl) - } - if vl := opt.ValidLifetime; vl != 0xeeff0011*time.Second { - t.Fatalf("Invalid Valid Lifetime. Expected 0xeeff0011, got %v", vl) - } - if pr := opt.PrefixLength(); pr != 36 { - t.Fatalf("Invalid Prefix Length. Expected 36, got %v", pr) + want := &OptIAPrefix{ + PreferredLifetime: 0xaabbccdd * time.Second, + ValidLifetime: 0xeeff0011 * time.Second, + Prefix: &net.IPNet{ + Mask: net.CIDRMask(36, 128), + IP: net.IPv6loopback, + }, + Options: PrefixOptions{[]Option{}}, } - if ip := opt.IPv6Prefix(); !ip.Equal(net.IPv6loopback) { - t.Fatalf("Invalid Prefix Length. Expected %v, got %v", net.IPv6loopback, ip) + if !reflect.DeepEqual(want, opt) { + t.Errorf("parseIAPrefix = %v, want %v", opt, want) } } @@ -46,10 +46,12 @@ func TestOptIAPrefixToBytes(t *testing.T) { opt := OptIAPrefix{ PreferredLifetime: 0xaabbccdd * time.Second, ValidLifetime: 0xeeff0011 * time.Second, - prefixLength: 36, - ipv6Prefix: net.IPv6zero, + Prefix: &net.IPNet{ + Mask: net.CIDRMask(36, 128), + IP: net.IPv6zero, + }, + Options: PrefixOptions{[]Option{OptElapsedTime(10 * time.Millisecond)}}, } - opt.Options.Add(OptElapsedTime(10 * time.Millisecond)) toBytes := opt.ToBytes() if !bytes.Equal(toBytes, buf) { t.Fatalf("Invalid ToBytes result. Expected %v, got %v", buf, toBytes) @@ -63,8 +65,7 @@ func TestOptIAPrefixToBytesDefault(t *testing.T) { 0, // prefixLength 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ipv6Prefix } - opt := OptIAPrefix{ - } + opt := OptIAPrefix{} toBytes := opt.ToBytes() if !bytes.Equal(toBytes, buf) { t.Fatalf("Invalid ToBytes result. Expected %v, got %v", buf, toBytes) @@ -96,17 +97,17 @@ func TestOptIAPrefixString(t *testing.T) { str := opt.String() require.Contains( t, str, - "ipv6prefix=2001:db8::", + "Prefix=2001:db8::/36", "String() should return the ipv6addr", ) require.Contains( t, str, - "preferredlifetime=1m", + "PreferredLifetime=1m", "String() should return the preferredlifetime", ) require.Contains( t, str, - "validlifetime=50s", + "ValidLifetime=50s", "String() should return the validlifetime", ) } |