summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4
diff options
context:
space:
mode:
authorChristopher Koch <chrisko@google.com>2019-04-03 21:02:28 -0700
committerChris K <c@chrisko.ch>2019-04-04 01:41:05 -0700
commitb40bd52ae58aee37cec9ef81008e24488350c98f (patch)
treed4255f100add3e70cfa8e15b1b826cd80243a915 /dhcpv4
parent625d653f51917b167cc2e53ef8fe595e85dd5fa4 (diff)
dhcpv4: add RFC3442 route options
Signed-off-by: Christopher Koch <chrisko@google.com>
Diffstat (limited to 'dhcpv4')
-rw-r--r--dhcpv4/dhcpv4.go15
-rw-r--r--dhcpv4/option_routes.go101
-rw-r--r--dhcpv4/option_routes_test.go64
-rw-r--r--dhcpv4/options.go3
-rw-r--r--dhcpv4/types.go8
5 files changed, 187 insertions, 4 deletions
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:
+//
+// <size of mask in number of bits>
+// <destination address, omitting octets that must be zero per mask>
+// <route IP>
+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",