Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restrict option parsers to their compatible options #487

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 88 additions & 5 deletions dhcpv6/dhcpv6message.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,96 @@ import (

const MessageHeaderSize = 4

// NewMessageOption returns new zero-value options for a DHCPv6 Message.
//
// Options listed in RFC 8415 Appendix B & C are used for this list.
func NewMessageOption(code OptionCode) Option {
var opt Option
switch code {
// The following are listed in RFC 8415 Appendix C.
case OptionClientID:
opt = &optClientID{}
case OptionServerID:
opt = &optServerID{}
case OptionIANA:
opt = &OptIANA{}
case OptionIATA:
opt = &OptIATA{}
case OptionIAPD:
opt = &OptIAPD{}
case OptionORO:
opt = &optRequestedOption{}
case OptionElapsedTime:
opt = &optElapsedTime{}
case OptionStatusCode:
opt = &OptStatusCode{}
case OptionUserClass:
opt = &OptUserClass{}
case OptionVendorClass:
opt = &OptVendorClass{}
case OptionVendorOpts:
opt = &OptVendorOpts{}
case OptionInformationRefreshTime:
opt = &optInformationRefreshTime{}

// RFC 3646 Section 5.
case OptionDNSRecursiveNameServer:
opt = &optDNS{}
case OptionDomainSearchList:
opt = &optDomainSearchList{}

// RFC 4704 Section 4.
case OptionFQDN:
opt = &OptFQDN{}

// RFC 5908 Section 5.
case OptionNTPServer:
opt = &OptNTPServer{}

// RFC 5970 Section 4.
case OptionBootfileURL:
opt = &optBootFileURL{}
case OptionBootfileParam:
opt = &optBootFileParam{}
case OptionClientArchType:
opt = &optClientArchType{}
case OptionNII:
opt = &OptNetworkInterfaceID{}

// RFC 7341 Section 6. Technically, this is compatible only with
// DHCPv4Query/Response message types. We don't have the ability to
// restrict by message type yet.
case OptionDHCPv4Msg:
opt = &OptDHCPv4Msg{}
case OptionDHCP4oDHCP6Server:
opt = &OptDHCP4oDHCP6Server{}

// RFC 7600 does not explicitly specify, but we assume this is not
// valid for Relay messages.
case Option4RD:
opt = &Opt4RD{}

// We have plenty of unimplemented options.
default:
opt = &OptionGeneric{OptionCode: code}
}
return opt
}

// MessageOptions are the options that may appear in a normal DHCPv6 message.
//
// RFC 3315 Appendix B lists the valid options that can be used.
// RFC 8415 Appendix C lists the valid options that can be used.
type MessageOptions struct {
Options
}

// FromBytes reads data into o and returns an error if the options are not a
// valid serialized representation of DHCPv6 message options per RFC 8415
// Appendix B.
func (mo *MessageOptions) FromBytes(data []byte) error {
return mo.FromBytesWithParser(data, NewMessageOption)
}

// ArchTypes returns the architecture type option.
func (mo MessageOptions) ArchTypes() iana.Archs {
opt := mo.GetOne(OptionClientArchType)
Expand Down Expand Up @@ -169,8 +252,8 @@ func (mo MessageOptions) BootFileURL() string {
if opt == nil {
return ""
}
if u, ok := opt.(optBootFileURL); ok {
return string(u)
if u, ok := opt.(*optBootFileURL); ok {
return u.url
}
return ""
}
Expand All @@ -181,8 +264,8 @@ func (mo MessageOptions) BootFileParam() []string {
if opt == nil {
return nil
}
if u, ok := opt.(optBootFileParam); ok {
return []string(u)
if u, ok := opt.(*optBootFileParam); ok {
return u.params
}
return nil
}
Expand Down
41 changes: 41 additions & 0 deletions dhcpv6/dhcpv6relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,40 @@ import (

const RelayHeaderSize = 34

// NewRelayOption returns new zero-value options for a DHCPv6 RelayMessage.
//
// Options listed in RFC 8415 Appendix B & C are used for this list, as well as
// RFC 4649 for the RemoteID, RFC 8357 for the relay port, and RFC 6939 for the
// client link-layer address.
func NewRelayOption(code OptionCode) Option {
var opt Option
switch code {
// RFC 8415 Appendix C
case OptionRelayMsg:
opt = &optRelayMsg{}
// RFC 8415 Appendix C
case OptionVendorOpts:
opt = &OptVendorOpts{}
// RFC 8415 Appendix C
case OptionInterfaceID:
opt = &optInterfaceID{}
// RFC 6939
case OptionClientLinkLayerAddr:
opt = &optClientLinkLayerAddress{}
// RFC 4649
case OptionRemoteID:
opt = &OptRemoteID{}
// RFC 8357
case OptionRelayPort:
opt = &optRelayPort{}

// We have plenty of unimplemented options.
default:
opt = &OptionGeneric{OptionCode: code}
}
return opt
}

// RelayOptions are the options valid for RelayForw and RelayRepl messages.
//
// RFC 3315 Appendix B defines them to be InterfaceID and RelayMsg options; RFC
Expand All @@ -20,6 +54,13 @@ type RelayOptions struct {
Options
}

// FromBytes reads data into o and returns an error if the options are not a
// valid serialized representation of DHCPv6 relay options per RFC 8415
// Appendix B.
func (ro *RelayOptions) FromBytes(data []byte) error {
return ro.FromBytesWithParser(data, NewRelayOption)
}

// RelayMessage returns the message embedded.
func (ro RelayOptions) RelayMessage() DHCPv6 {
opt := ro.Options.GetOne(OptionRelayMsg)
Expand Down
70 changes: 47 additions & 23 deletions dhcpv6/option_4rd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import (
)

// Opt4RD represents a 4RD option. It is only a container for 4RD_*_RULE options
//
// Defined in RFC 7600 Section 4.9.
type Opt4RD struct {
Options
Options FourRDOptions
}

// Code returns the Option Code for this option
Expand All @@ -32,12 +34,36 @@ func (op *Opt4RD) LongString(indentSpace int) string {
return fmt.Sprintf("%s: Options=%v", op.Code(), op.Options.LongString(indentSpace))
}

// ParseOpt4RD builds an Opt4RD structure from a sequence of bytes.
// FromBytes builds an Opt4RD structure from a sequence of bytes.
// The input data does not include option code and length bytes
func ParseOpt4RD(data []byte) (*Opt4RD, error) {
var opt Opt4RD
err := opt.Options.FromBytes(data)
return &opt, err
func (op *Opt4RD) FromBytes(data []byte) error {
return op.Options.FromBytes(data)
}

// New4RDOption returns new zero-value options for DHCPv6 4RD suboption.
//
// Options listed in RFC 7600 Section 4.9 are eligible.
func New4RDOption(code OptionCode) Option {
var opt Option
switch code {
case Option4RDMapRule:
opt = &Opt4RDMapRule{}
case Option4RDNonMapRule:
opt = &Opt4RDNonMapRule{}
}
return opt
}

// FourRDOptions are 4RD suboptions as defined in RFC 7600 Section 4.9.
type FourRDOptions struct {
Options
}

// FromBytes reads data into fo and returns an error if the options are not a
// valid serialized representation of DHCPv6 4RD options per RFC 7600 Section
// 4.9.
func (fo *FourRDOptions) FromBytes(data []byte) error {
return fo.FromBytesWithParser(data, New4RDOption)
}

// Opt4RDMapRule represents a 4RD Mapping Rule option
Expand Down Expand Up @@ -105,18 +131,17 @@ func (op *Opt4RDMapRule) String() string {
op.Code(), op.Prefix4.String(), op.Prefix6.String(), op.EABitsLength, op.WKPAuthorized)
}

// ParseOpt4RDMapRule builds an Opt4RDMapRule structure from a sequence of bytes.
// FromBytes builds an Opt4RDMapRule structure from a sequence of bytes.
// The input data does not include option code and length bytes.
func ParseOpt4RDMapRule(data []byte) (*Opt4RDMapRule, error) {
var opt Opt4RDMapRule
func (op *Opt4RDMapRule) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
opt.Prefix4.Mask = net.CIDRMask(int(buf.Read8()), 32)
opt.Prefix6.Mask = net.CIDRMask(int(buf.Read8()), 128)
opt.EABitsLength = buf.Read8()
opt.WKPAuthorized = (buf.Read8() & opt4RDWKPAuthorizedMask) != 0
opt.Prefix4.IP = net.IP(buf.CopyN(net.IPv4len))
opt.Prefix6.IP = net.IP(buf.CopyN(net.IPv6len))
return &opt, buf.FinError()
op.Prefix4.Mask = net.CIDRMask(int(buf.Read8()), 32)
op.Prefix6.Mask = net.CIDRMask(int(buf.Read8()), 128)
op.EABitsLength = buf.Read8()
op.WKPAuthorized = (buf.Read8() & opt4RDWKPAuthorizedMask) != 0
op.Prefix4.IP = net.IP(buf.CopyN(net.IPv4len))
op.Prefix6.IP = net.IP(buf.CopyN(net.IPv6len))
return buf.FinError()
}

// Opt4RDNonMapRule represents 4RD parameters other than mapping rules
Expand Down Expand Up @@ -165,21 +190,20 @@ func (op *Opt4RDNonMapRule) String() string {
op.Code(), op.HubAndSpoke, tClass, op.DomainPMTU)
}

// ParseOpt4RDNonMapRule builds an Opt4RDNonMapRule structure from a sequence of bytes.
// FromBytes builds an Opt4RDNonMapRule structure from a sequence of bytes.
// The input data does not include option code and length bytes
func ParseOpt4RDNonMapRule(data []byte) (*Opt4RDNonMapRule, error) {
var opt Opt4RDNonMapRule
func (op *Opt4RDNonMapRule) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
flags := buf.Read8()

opt.HubAndSpoke = flags&opt4RDHubAndSpokeMask != 0
op.HubAndSpoke = flags&opt4RDHubAndSpokeMask != 0

tClass := buf.Read8()
if flags&opt4RDTrafficClassMask != 0 {
opt.TrafficClass = &tClass
op.TrafficClass = &tClass
}

opt.DomainPMTU = buf.Read16()
op.DomainPMTU = buf.Read16()

return &opt, buf.FinError()
return buf.FinError()
}
57 changes: 37 additions & 20 deletions dhcpv6/option_4rd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ package dhcpv6

import (
"net"
"reflect"
"testing"

"github.com/stretchr/testify/require"
)

func TestOpt4RDNonMapRuleParse(t *testing.T) {
data := []byte{0x81, 0xaa, 0x05, 0xd4}
opt, err := ParseOpt4RDNonMapRule(data)
var opt Opt4RDNonMapRule
err := opt.FromBytes(data)
require.NoError(t, err)
require.True(t, opt.HubAndSpoke)
require.NotNil(t, opt.TrafficClass)
Expand All @@ -18,7 +20,8 @@ func TestOpt4RDNonMapRuleParse(t *testing.T) {

// Remove the TrafficClass flag and check value is ignored
data[0] = 0x80
opt, err = ParseOpt4RDNonMapRule(data)
opt = Opt4RDNonMapRule{}
err = opt.FromBytes(data)
require.NoError(t, err)
require.True(t, opt.HubAndSpoke)
require.Nil(t, opt.TrafficClass)
Expand Down Expand Up @@ -77,7 +80,8 @@ func TestOpt4RDMapRuleParse(t *testing.T) {
append(ip4addr.To4(), ip6addr...)...,
)

opt, err := ParseOpt4RDMapRule(data)
var opt Opt4RDMapRule
err = opt.FromBytes(data)
require.NoError(t, err)
require.EqualValues(t, *ip6net, opt.Prefix6)
require.EqualValues(t, *ip4net, opt.Prefix4)
Expand Down Expand Up @@ -141,30 +145,43 @@ func TestOpt4RDMapRuleString(t *testing.T) {
func TestOpt4RDRoundTrip(t *testing.T) {
var tClass uint8 = 0xaa
opt := Opt4RD{
Options: Options{
&Opt4RDMapRule{
Prefix4: net.IPNet{
IP: net.IPv4(100, 64, 0, 238).To4(),
Mask: net.CIDRMask(24, 32),
Options: FourRDOptions{
Options: Options{
&Opt4RDMapRule{
Prefix4: net.IPNet{
IP: net.IPv4(100, 64, 0, 238).To4(),
Mask: net.CIDRMask(24, 32),
},
Prefix6: net.IPNet{
IP: net.ParseIP("2001:db8::1234:5678:0:aabb"),
Mask: net.CIDRMask(80, 128),
},
EABitsLength: 32,
WKPAuthorized: true,
},
Prefix6: net.IPNet{
IP: net.ParseIP("2001:db8::1234:5678:0:aabb"),
Mask: net.CIDRMask(80, 128),
&Opt4RDNonMapRule{
HubAndSpoke: true,
TrafficClass: &tClass,
DomainPMTU: 9000,
},
EABitsLength: 32,
WKPAuthorized: true,
},
&Opt4RDNonMapRule{
HubAndSpoke: true,
TrafficClass: &tClass,
DomainPMTU: 9000,
},
},
}

rtOpt, err := ParseOpt4RD(opt.ToBytes())
var rtOpt Opt4RD
err := rtOpt.FromBytes(opt.ToBytes())

require.NoError(t, err)
require.NotNil(t, rtOpt)
require.Equal(t, opt, *rtOpt)
require.Equal(t, opt, rtOpt)

var mo MessageOptions
mo.Options.Add(&opt)

var got MessageOptions
if err := got.FromBytes(mo.ToBytes()); err != nil {
t.Errorf("FromBytes = %v", err)
} else if !reflect.DeepEqual(mo, got) {
t.Errorf("FromBytes = %v, want %v", got, mo)
}
}
8 changes: 2 additions & 6 deletions dhcpv6/option_archtype.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ func (op optClientArchType) String() string {
return fmt.Sprintf("%s: %s", op.Code(), op.Archs)
}

// parseOptClientArchType builds an OptClientArchType structure from
// a sequence of bytes The input data does not include option code and
// length bytes.
func parseOptClientArchType(data []byte) (*optClientArchType, error) {
var opt optClientArchType
return &opt, opt.FromBytes(data)
func (op *optClientArchType) FromBytes(p []byte) error {
return op.Archs.FromBytes(p)
}
Loading