From 3f14f7f8bd9cd69eb47abcc82768498a03d1e74a Mon Sep 17 00:00:00 2001 From: Anatole Denis Date: Wed, 5 Aug 2020 20:15:27 +0200 Subject: dhcpv6: Handle IA_TA options This creates support for IA_TA options, based on and reusing the blocks from IA_NA, to which it is extremely similar Signed-off-by: Anatole Denis --- dhcpv6/dhcpv6message.go | 19 +++++ dhcpv6/modifiers.go | 17 +++++ dhcpv6/option_temporaryaddress.go | 48 +++++++++++++ dhcpv6/option_temporaryaddress_test.go | 123 +++++++++++++++++++++++++++++++++ dhcpv6/options.go | 2 + 5 files changed, 209 insertions(+) create mode 100644 dhcpv6/option_temporaryaddress.go create mode 100644 dhcpv6/option_temporaryaddress_test.go (limited to 'dhcpv6') diff --git a/dhcpv6/dhcpv6message.go b/dhcpv6/dhcpv6message.go index a4da0a4..f3ed4ef 100644 --- a/dhcpv6/dhcpv6message.go +++ b/dhcpv6/dhcpv6message.go @@ -67,6 +67,25 @@ func (mo MessageOptions) OneIANA() *OptIANA { return ianas[0] } +// IATA returns all Identity Association for Temporary Address options. +func (mo MessageOptions) IATA() []*OptIATA { + opts := mo.Get(OptionIANA) + var iatas []*OptIATA + for _, o := range opts { + iatas = append(iatas, o.(*OptIATA)) + } + return iatas +} + +// OneIATA returns the first IATA option. +func (mo MessageOptions) OneIATA() *OptIATA { + iatas := mo.IATA() + if len(iatas) == 0 { + return nil + } + return iatas[0] +} + // IAPD returns all Identity Association for Prefix Delegation options. func (mo MessageOptions) IAPD() []*OptIAPD { opts := mo.Get(OptionIAPD) diff --git a/dhcpv6/modifiers.go b/dhcpv6/modifiers.go index 8099795..fbbad23 100644 --- a/dhcpv6/modifiers.go +++ b/dhcpv6/modifiers.go @@ -90,6 +90,23 @@ func WithIAID(iaid [4]byte) Modifier { } } +// WithIATA adds or updates an OptIANA option with the provided IAAddress +// options +func WithIATA(addrs ...OptIAAddress) Modifier { + return func(d DHCPv6) { + if msg, ok := d.(*Message); ok { + iata := msg.Options.OneIATA() + if iata == nil { + iata = &OptIATA{} + } + for _, addr := range addrs { + iata.Options.Add(&addr) + } + msg.UpdateOption(iata) + } + } +} + // WithDNS adds or updates an OptDNSRecursiveNameServer func WithDNS(dnses ...net.IP) Modifier { return WithOption(OptDNS(dnses...)) diff --git a/dhcpv6/option_temporaryaddress.go b/dhcpv6/option_temporaryaddress.go new file mode 100644 index 0000000..3aff1d4 --- /dev/null +++ b/dhcpv6/option_temporaryaddress.go @@ -0,0 +1,48 @@ +package dhcpv6 + +import ( + "fmt" + + "github.com/u-root/u-root/pkg/uio" +) + +// OptIATA implements the identity association for non-temporary addresses +// option. +// +// This module defines the OptIATA structure. +// https://www.ietf.org/rfc/rfc8415.txt +type OptIATA struct { + IaId [4]byte + Options IdentityOptions +} + +// Code returns the option code for an IA_TA +func (op *OptIATA) Code() OptionCode { + return OptionIATA +} + +// ToBytes serializes IATA to DHCPv6 bytes. +func (op *OptIATA) ToBytes() []byte { + buf := uio.NewBigEndianBuffer(nil) + buf.WriteBytes(op.IaId[:]) + buf.WriteBytes(op.Options.ToBytes()) + return buf.Data() +} + +func (op *OptIATA) String() string { + return fmt.Sprintf("IATA: {IAID=%v, options=%v}", + op.IaId, op.Options) +} + +// ParseOptIATA builds an OptIATA structure from a sequence of bytes. The +// input data does not include option code and length bytes. +func ParseOptIATA(data []byte) (*OptIATA, error) { + var opt OptIATA + buf := uio.NewBigEndianBuffer(data) + buf.ReadBytes(opt.IaId[:]) + + if err := opt.Options.FromBytes(buf.ReadAll()); err != nil { + return nil, err + } + return &opt, buf.FinError() +} diff --git a/dhcpv6/option_temporaryaddress_test.go b/dhcpv6/option_temporaryaddress_test.go new file mode 100644 index 0000000..de7fe39 --- /dev/null +++ b/dhcpv6/option_temporaryaddress_test.go @@ -0,0 +1,123 @@ +package dhcpv6 + +import ( + "net" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestOptIATAParseOptIATA(t *testing.T) { + data := []byte{ + 1, 0, 0, 0, // IAID + 0, 5, 0, 0x18, 0x24, 1, 0xdb, 0, 0x30, 0x10, 0xc0, 0x8f, 0xfa, 0xce, 0, 0, 0, 0x44, 0, 0, 0, 0, 0xb2, 0x7a, 0, 0, 0xc0, 0x8a, // options + } + opt, err := ParseOptIATA(data) + require.NoError(t, err) + require.Equal(t, OptionIATA, opt.Code()) +} + +func TestOptIATAParseOptIATAInvalidLength(t *testing.T) { + data := []byte{ + 1, 0, 0, // truncated IAID + } + _, err := ParseOptIATA(data) + require.Error(t, err) +} + +func TestOptIATAParseOptIATAInvalidOptions(t *testing.T) { + data := []byte{ + 1, 0, 0, 0, // IAID + 0, 5, 0, 0x18, 0x24, 1, 0xdb, 0, 0x30, 0x10, 0xc0, 0x8f, 0xfa, 0xce, 0, 0, 0, 0x44, 0, 0, 0, 0, 0xb2, 0x7a, // truncated options + } + _, err := ParseOptIATA(data) + require.Error(t, err) +} + +func TestOptIATAGetOneOption(t *testing.T) { + oaddr := &OptIAAddress{ + IPv6Addr: net.ParseIP("::1"), + } + opt := OptIATA{ + Options: IdentityOptions{[]Option{&OptStatusCode{}, oaddr}}, + } + require.Equal(t, oaddr, opt.Options.OneAddress()) +} + +func TestOptIATAAddOption(t *testing.T) { + opt := OptIATA{} + opt.Options.Add(OptElapsedTime(0)) + require.Equal(t, 1, len(opt.Options.Options)) + require.Equal(t, OptionElapsedTime, opt.Options.Options[0].Code()) +} + +func TestOptIATAGetOneOptionMissingOpt(t *testing.T) { + oaddr := &OptIAAddress{ + IPv6Addr: net.ParseIP("::1"), + } + opt := OptIATA{ + Options: IdentityOptions{[]Option{&OptStatusCode{}, oaddr}}, + } + require.Equal(t, nil, opt.Options.GetOne(OptionDNSRecursiveNameServer)) +} + +func TestOptIATADelOption(t *testing.T) { + optiaaddr := OptIAAddress{} + optsc := OptStatusCode{} + + iana1 := OptIATA{ + Options: IdentityOptions{[]Option{ + &optsc, + &optiaaddr, + &optiaaddr, + }}, + } + iana1.Options.Del(OptionIAAddr) + require.Equal(t, iana1.Options.Options, Options{&optsc}) + + iana2 := OptIATA{ + Options: IdentityOptions{[]Option{ + &optiaaddr, + &optsc, + &optiaaddr, + }}, + } + iana2.Options.Del(OptionIAAddr) + require.Equal(t, iana2.Options.Options, Options{&optsc}) +} + +func TestOptIATAToBytes(t *testing.T) { + opt := OptIATA{ + IaId: [4]byte{1, 2, 3, 4}, + Options: IdentityOptions{[]Option{ + OptElapsedTime(10 * time.Millisecond), + }}, + } + expected := []byte{ + 1, 2, 3, 4, // IA ID + 0, 8, 0, 2, 0x00, 0x01, + } + require.Equal(t, expected, opt.ToBytes()) +} + +func TestOptIATAString(t *testing.T) { + data := []byte{ + 1, 0, 0, 0, // IAID + 0, 5, 0, 0x18, 0x24, 1, 0xdb, 0, 0x30, 0x10, 0xc0, 0x8f, 0xfa, 0xce, 0, 0, 0, 0x44, 0, 0, 0, 0, 0xb2, 0x7a, 0, 0, 0xc0, 0x8a, // options + } + opt, err := ParseOptIATA(data) + require.NoError(t, err) + + str := opt.String() + require.Contains( + t, str, + "IAID=[1 0 0 0]", + "String() should return the IAID", + ) + require.Contains( + t, str, + "options={", + "String() should return a list of options", + ) +} diff --git a/dhcpv6/options.go b/dhcpv6/options.go index a3afde0..16bc8d7 100644 --- a/dhcpv6/options.go +++ b/dhcpv6/options.go @@ -45,6 +45,8 @@ func ParseOption(code OptionCode, optData []byte) (Option, error) { opt, err = parseOptServerID(optData) case OptionIANA: opt, err = ParseOptIANA(optData) + case OptionIATA: + opt, err = ParseOptIATA(optData) case OptionIAAddr: opt, err = ParseOptIAAddress(optData) case OptionORO: -- cgit v1.2.3