From b40bd52ae58aee37cec9ef81008e24488350c98f Mon Sep 17 00:00:00 2001 From: Christopher Koch Date: Wed, 3 Apr 2019 21:02:28 -0700 Subject: dhcpv4: add RFC3442 route options Signed-off-by: Christopher Koch --- dhcpv4/dhcpv4.go | 15 +++++++ dhcpv4/option_routes.go | 101 +++++++++++++++++++++++++++++++++++++++++++ dhcpv4/option_routes_test.go | 64 +++++++++++++++++++++++++++ dhcpv4/options.go | 3 ++ dhcpv4/types.go | 8 ++-- 5 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 dhcpv4/option_routes.go create mode 100644 dhcpv4/option_routes_test.go diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go index 5ab3561..cfb2f9a 100644 --- a/dhcpv4/dhcpv4.go +++ b/dhcpv4/dhcpv4.go @@ -520,6 +520,21 @@ func (d *DHCPv4) Router() []net.IP { return GetIPs(OptionRouter, d.Options) } +// ClasslessStaticRoute parses the DHCPv4 Classless Static Route option if present. +// +// The Classless Static Route option is described by RFC 3442. +func (d *DHCPv4) ClasslessStaticRoute() []*Route { + v := d.Options.Get(OptionClasslessStaticRoute) + if v == nil { + return nil + } + var routes Routes + if err := routes.FromBytes(v); err != nil { + return nil + } + return routes +} + // NTPServers parses the DHCPv4 NTP Servers option if present. // // The NTP servers option is described by RFC 2132, Section 8.3. diff --git a/dhcpv4/option_routes.go b/dhcpv4/option_routes.go new file mode 100644 index 0000000..603273a --- /dev/null +++ b/dhcpv4/option_routes.go @@ -0,0 +1,101 @@ +package dhcpv4 + +import ( + "fmt" + "net" + "strings" + + "github.com/u-root/u-root/pkg/uio" +) + +// Route is a classless static route as per RFC 3442. +type Route struct { + // Dest is the destination network. + Dest *net.IPNet + + // Router is the router to use for the given destination network. + Router net.IP +} + +// Marshal implements uio.Marshaler. +// +// Format described in RFC 3442: +// +// +// +// +func (r Route) Marshal(buf *uio.Lexer) { + ones, _ := r.Dest.Mask.Size() + buf.Write8(uint8(ones)) + + // Only write the non-zero octets. + dstLen := (ones + 7) / 8 + buf.WriteBytes(r.Dest.IP.To4()[:dstLen]) + + buf.WriteBytes(r.Router.To4()) +} + +// Unmarshal implements uio.Unmarshaler. +func (r *Route) Unmarshal(buf *uio.Lexer) error { + maskSize := buf.Read8() + r.Dest = &net.IPNet{ + IP: make([]byte, net.IPv4len), + Mask: net.CIDRMask(int(maskSize), 32), + } + + dstLen := (maskSize + 7) / 8 + buf.ReadBytes(r.Dest.IP[:dstLen]) + + r.Router = buf.CopyN(net.IPv4len) + return buf.Error() +} + +// String prints the destination network and router IP. +func (r *Route) String() string { + return fmt.Sprintf("route to %s via %s", r.Dest, r.Router) +} + +// Routes is a collection of network routes. +type Routes []*Route + +// FromBytes parses routes from a set of bytes as described by RFC 3442. +func (r *Routes) FromBytes(p []byte) error { + buf := uio.NewBigEndianBuffer(p) + for buf.Has(1) { + var route Route + if err := route.Unmarshal(buf); err != nil { + return err + } + *r = append(*r, &route) + } + return buf.FinError() +} + +// ToBytes marshals a set of routes as described by RFC 3442. +func (r Routes) ToBytes() []byte { + buf := uio.NewBigEndianBuffer(nil) + for _, route := range r { + route.Marshal(buf) + } + return buf.Data() +} + +// String prints all routes. +func (r Routes) String() string { + s := make([]string, 0, len(r)) + for _, route := range r { + s = append(s, route.String()) + } + return strings.Join(s, "; ") +} + +// OptClasslessStaticRoute returns a new DHCPv4 Classless Static Route +// option. +// +// The Classless Static Route option is described by RFC 3442. +func OptClasslessStaticRoute(routes ...*Route) Option { + return Option{ + Code: OptionClasslessStaticRoute, + Value: Routes(routes), + } +} diff --git a/dhcpv4/option_routes_test.go b/dhcpv4/option_routes_test.go new file mode 100644 index 0000000..19e331b --- /dev/null +++ b/dhcpv4/option_routes_test.go @@ -0,0 +1,64 @@ +package dhcpv4 + +import ( + "net" + "reflect" + "testing" +) + +func mustParseIPNet(s string) *net.IPNet { + _, ipnet, err := net.ParseCIDR(s) + if err != nil { + panic(err) + } + return ipnet +} + +func TestParseRoutes(t *testing.T) { + for _, tt := range []struct { + p []byte + want Routes + err error + }{ + { + p: []byte{32, 10, 2, 3, 4, 0, 0, 0, 0}, + want: Routes{ + &Route{ + Dest: mustParseIPNet("10.2.3.4/32"), + Router: net.IP{0, 0, 0, 0}, + }, + }, + }, + { + p: []byte{0, 0, 0, 0, 0}, + want: Routes{ + &Route{ + Dest: mustParseIPNet("0.0.0.0/0"), + Router: net.IP{0, 0, 0, 0}, + }, + }, + }, + { + p: []byte{32, 10, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + want: Routes{ + &Route{ + Dest: mustParseIPNet("10.2.3.4/32"), + Router: net.IP{0, 0, 0, 0}, + }, + &Route{ + Dest: mustParseIPNet("0.0.0.0/0"), + Router: net.IP{0, 0, 0, 0}, + }, + }, + }, + } { + var r Routes + if err := r.FromBytes(tt.p); err != tt.err { + t.Errorf("FromBytes(%v) = %v, want %v", tt.p, err, tt.err) + } + + if !reflect.DeepEqual(r, tt.want) { + t.Errorf("FromBytes(%v) = %v, want %v", tt.p, r, tt.want) + } + } +} diff --git a/dhcpv4/options.go b/dhcpv4/options.go index 4968130..37607b7 100644 --- a/dhcpv4/options.go +++ b/dhcpv4/options.go @@ -344,6 +344,9 @@ func getOption(code OptionCode, data []byte, vendorDecoder OptionDecoder) fmt.St case OptionVendorSpecificInformation: d = vendorDecoder + + case OptionClasslessStaticRoute: + d = &Routes{} } if d != nil && d.FromBytes(data) == nil { return d diff --git a/dhcpv4/types.go b/dhcpv4/types.go index e1762cc..6a403e8 100644 --- a/dhcpv4/types.go +++ b/dhcpv4/types.go @@ -247,8 +247,8 @@ const ( OptionNameServiceSearch optionCode = 117 OptionSubnetSelection optionCode = 118 OptionDNSDomainSearchList optionCode = 119 - OptionSIPServersDHCPOption optionCode = 120 - OptionClasslessStaticRouteOption optionCode = 121 + OptionSIPServers optionCode = 120 + OptionClasslessStaticRoute optionCode = 121 OptionCCC optionCode = 122 OptionGeoConf optionCode = 123 OptionVendorIdentifyingVendorClass optionCode = 124 @@ -410,8 +410,8 @@ var optionCodeToString = map[OptionCode]string{ OptionNameServiceSearch: "Name Service Search", OptionSubnetSelection: "Subnet Selection", OptionDNSDomainSearchList: "DNS Domain Search List", - OptionSIPServersDHCPOption: "SIP Servers DHCP Option", - OptionClasslessStaticRouteOption: "Classless Static Route Option", + OptionSIPServers: "SIP Servers", + OptionClasslessStaticRoute: "Classless Static Route", OptionCCC: "CCC, CableLabs Client Configuration", OptionGeoConf: "GeoConf", OptionVendorIdentifyingVendorClass: "Vendor-Identifying Vendor Class", -- cgit v1.2.3