From 07b3f0e1882cfbec6b3cde9d8d9b47fe7121efb2 Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Wed, 17 Jan 2024 07:37:47 -0500 Subject: [PATCH] ndp: add RAFlagsExtension option Signed-off-by: Matt Layher --- internal/ndpcmd/print.go | 2 + option.go | 60 +++++++++++++++++++++++++++++ option_test.go | 82 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+) diff --git a/internal/ndpcmd/print.go b/internal/ndpcmd/print.go index 14ed1b0..dce82da 100644 --- a/internal/ndpcmd/print.go +++ b/internal/ndpcmd/print.go @@ -171,6 +171,8 @@ func optStr(o ndp.Option) string { servers := strings.Join(ss, ", ") return fmt.Sprintf("recursive DNS servers: lifetime: %s, servers: %s", o.Lifetime, servers) + case *ndp.RAFlagsExtension: + return fmt.Sprintf("RA flags extension: [%# 02x]", o.Flags) case *ndp.DNSSearchList: return fmt.Sprintf("DNS search list: lifetime: %s, domain names: %s", o.Lifetime, strings.Join(o.DomainNames, ", ")) case *ndp.CaptivePortal: diff --git a/option.go b/option.go index bec3007..a518e76 100644 --- a/option.go +++ b/option.go @@ -12,6 +12,7 @@ import ( "net" "net/netip" "net/url" + "slices" "strings" "time" "unicode" @@ -40,6 +41,7 @@ const ( optNonce = 14 optRouteInformation = 24 optRDNSS = 25 + optRAFlagsExtension = 26 optDNSSL = 31 optCaptivePortal = 37 ) @@ -766,6 +768,62 @@ func (cp *CaptivePortal) unmarshal(b []byte) error { return nil } +// A RAFlagsExtension is a Router Advertisement Flags Extension (or Expansion) +// option, as described in RFC 5175, Section 4. +type RAFlagsExtension struct { + Flags RAFlags +} + +// RAFlags is a bitmask of Router Advertisement flags contained within an +// RAFlagsExtension. +type RAFlags []byte + +// Code implements Option. +func (*RAFlagsExtension) Code() byte { return optRAFlagsExtension } + +func (ra *RAFlagsExtension) marshal() ([]byte, error) { + // "MUST NOT be added to a Router Advertisement message if no flags in the + // option are set." + i := slices.IndexFunc(ra.Flags, func(b byte) bool { return b != 0x00 }) + if i == -1 { + return nil, errors.New("ndp: RA flags extension requires one or more flags to be set") + } + + // Enforce the option size matches the next unit of 8 bytes including 2 + // bytes for code and length. + l := len(ra.Flags) + if r := (l + 2) % 8; r != 0 { + return nil, errors.New("ndp: RA flags extension length is invalid") + } + + value := make([]byte, l) + copy(value, ra.Flags) + + raw := &RawOption{ + Type: ra.Code(), + Length: (uint8(l) + 2) / 8, + Value: value, + } + + return raw.marshal() +} + +func (ra *RAFlagsExtension) unmarshal(b []byte) error { + raw := new(RawOption) + if err := raw.unmarshal(b); err != nil { + return err + } + + // Don't allow short bytes. + if len(raw.Value) < 6 { + return errors.New("ndp: RA Flags Extension too short") + } + + // raw already made a copy. + ra.Flags = raw.Value + return nil +} + // A Nonce is a Nonce option, as described in RFC 3971, Section 5.3.2. type Nonce struct { b []byte @@ -926,6 +984,8 @@ func parseOptions(b []byte) ([]Option, error) { o = new(RouteInformation) case optRDNSS: o = new(RecursiveDNSServer) + case optRAFlagsExtension: + o = new(RAFlagsExtension) case optDNSSL: o = new(DNSSearchList) case optCaptivePortal: diff --git a/option_test.go b/option_test.go index 7232144..740363b 100644 --- a/option_test.go +++ b/option_test.go @@ -59,6 +59,10 @@ func TestOptionMarshalUnmarshal(t *testing.T) { name: "recursive DNS servers", subs: rdnssTests(), }, + { + name: "RA flags extension", + subs: raFlagsExtensionTests(), + }, { name: "DNS search list", subs: dnsslTests(), @@ -276,6 +280,20 @@ func TestOptionUnmarshalError(t *testing.T) { }, }, }, + { + name: "ra flags extension", + o: &RAFlagsExtension{}, + subs: []sub{ + { + name: "short flags", + bs: [][]byte{ + {26, 1}, + // Short flags. + ndptest.Zero(5), + }, + }, + }, + }, { name: "dnssl", o: &DNSSearchList{}, @@ -776,6 +794,70 @@ func rdnssTests() []optionSub { } } +func raFlagsExtensionTests() []optionSub { + return []optionSub{ + { + name: "bad, no flags", + os: []Option{ + &RAFlagsExtension{}, + }, + }, + { + name: "bad, zero flags", + os: []Option{ + &RAFlagsExtension{ + Flags: RAFlags(ndptest.Zero(6)), + }, + }, + }, + { + name: "bad, short padding", + os: []Option{ + &RAFlagsExtension{ + Flags: RAFlags{ + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + }, + }, + }, + }, + { + name: "ok, length 1", + os: []Option{ + &RAFlagsExtension{ + Flags: RAFlags{0x80, 0x00, 0x00, 0x00, 0x00, 0x00}, + }, + }, + bs: [][]byte{ + {26, 1}, + // Short values. + {128, 0, 0, 0, 0, 0}, + }, + ok: true, + }, + { + name: "ok, length 2", + os: []Option{ + &RAFlagsExtension{ + Flags: RAFlags{ + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + }, + }, + bs: [][]byte{ + {26, 2}, + // Short values. + { + 128, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }, + }, + ok: true, + }, + } +} + func dnsslTests() []optionSub { return []optionSub{ {