Browse Source

route: add support for the manipulation of routing informaion

This change adds the Marshal method of RouteMessage to make it
possible to exchange route messages between userspace processes and
the kernel for the manipulation of routing information base inside the
kernel.

Change-Id: I0cf2c1a391820f41eb9c5eac1c172598cb2e1533
Reviewed-on: https://go-review.googlesource.com/36077
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Mikio Hara 9 years ago
parent
commit
41bba8d80b

+ 148 - 4
route/address.go

@@ -24,6 +24,39 @@ type LinkAddr struct {
 // Family implements the Family method of Addr interface.
 func (a *LinkAddr) Family() int { return sysAF_LINK }
 
+func (a *LinkAddr) lenAndSpace() (int, int) {
+	l := 8 + len(a.Name) + len(a.Addr)
+	return l, roundup(l)
+}
+
+func (a *LinkAddr) marshal(b []byte) (int, error) {
+	l, ll := a.lenAndSpace()
+	if len(b) < ll {
+		return 0, errShortBuffer
+	}
+	nlen, alen := len(a.Name), len(a.Addr)
+	if nlen > 255 || alen > 255 {
+		return 0, errInvalidAddr
+	}
+	b[0] = byte(l)
+	b[1] = sysAF_LINK
+	if a.Index > 0 {
+		nativeEndian.PutUint16(b[2:4], uint16(a.Index))
+	}
+	data := b[8:]
+	if nlen > 0 {
+		b[5] = byte(nlen)
+		copy(data[:nlen], a.Addr)
+		data = data[nlen:]
+	}
+	if alen > 0 {
+		b[6] = byte(alen)
+		copy(data[:alen], a.Name)
+		data = data[alen:]
+	}
+	return ll, nil
+}
+
 func parseLinkAddr(b []byte) (Addr, error) {
 	if len(b) < 8 {
 		return nil, errInvalidAddr
@@ -90,6 +123,21 @@ type Inet4Addr struct {
 // Family implements the Family method of Addr interface.
 func (a *Inet4Addr) Family() int { return sysAF_INET }
 
+func (a *Inet4Addr) lenAndSpace() (int, int) {
+	return sizeofSockaddrInet, roundup(sizeofSockaddrInet)
+}
+
+func (a *Inet4Addr) marshal(b []byte) (int, error) {
+	l, ll := a.lenAndSpace()
+	if len(b) < ll {
+		return 0, errShortBuffer
+	}
+	b[0] = byte(l)
+	b[1] = sysAF_INET
+	copy(b[4:8], a.IP[:])
+	return ll, nil
+}
+
 // An Inet6Addr represents an internet address for IPv6.
 type Inet6Addr struct {
 	IP     [16]byte // IP address
@@ -99,18 +147,36 @@ type Inet6Addr struct {
 // Family implements the Family method of Addr interface.
 func (a *Inet6Addr) Family() int { return sysAF_INET6 }
 
+func (a *Inet6Addr) lenAndSpace() (int, int) {
+	return sizeofSockaddrInet6, roundup(sizeofSockaddrInet6)
+}
+
+func (a *Inet6Addr) marshal(b []byte) (int, error) {
+	l, ll := a.lenAndSpace()
+	if len(b) < ll {
+		return 0, errShortBuffer
+	}
+	b[0] = byte(l)
+	b[1] = sysAF_INET6
+	copy(b[8:24], a.IP[:])
+	if a.ZoneID > 0 {
+		nativeEndian.PutUint32(b[24:28], uint32(a.ZoneID))
+	}
+	return ll, nil
+}
+
 // parseInetAddr parses b as an internet address for IPv4 or IPv6.
 func parseInetAddr(af int, b []byte) (Addr, error) {
 	switch af {
 	case sysAF_INET:
-		if len(b) < 16 {
+		if len(b) < sizeofSockaddrInet {
 			return nil, errInvalidAddr
 		}
 		a := &Inet4Addr{}
 		copy(a.IP[:], b[4:8])
 		return a, nil
 	case sysAF_INET6:
-		if len(b) < 28 {
+		if len(b) < sizeofSockaddrInet6 {
 			return nil, errInvalidAddr
 		}
 		a := &Inet6Addr{ZoneID: int(nativeEndian.Uint32(b[24:28]))}
@@ -174,7 +240,7 @@ func parseKernelInetAddr(af int, b []byte) (int, Addr, error) {
 		off6 = 8 // offset of in6_addr
 	)
 	switch {
-	case b[0] == 28: // size of sockaddr_in6
+	case b[0] == sizeofSockaddrInet6:
 		a := &Inet6Addr{}
 		copy(a.IP[:], b[off6:off6+16])
 		return int(b[0]), a, nil
@@ -186,7 +252,7 @@ func parseKernelInetAddr(af int, b []byte) (int, Addr, error) {
 			copy(a.IP[:], b[l-off6:l])
 		}
 		return int(b[0]), a, nil
-	case b[0] == 16: // size of sockaddr_in
+	case b[0] == sizeofSockaddrInet:
 		a := &Inet4Addr{}
 		copy(a.IP[:], b[off4:off4+4])
 		return int(b[0]), a, nil
@@ -211,6 +277,24 @@ type DefaultAddr struct {
 // Family implements the Family method of Addr interface.
 func (a *DefaultAddr) Family() int { return a.af }
 
+func (a *DefaultAddr) lenAndSpace() (int, int) {
+	l := len(a.Raw)
+	return l, roundup(l)
+}
+
+func (a *DefaultAddr) marshal(b []byte) (int, error) {
+	l, ll := a.lenAndSpace()
+	if len(b) < ll {
+		return 0, errShortBuffer
+	}
+	if l > 255 {
+		return 0, errInvalidAddr
+	}
+	b[1] = byte(l)
+	copy(b[:l], a.Raw)
+	return ll, nil
+}
+
 func parseDefaultAddr(b []byte) (Addr, error) {
 	if len(b) < 2 || len(b) < int(b[0]) {
 		return nil, errInvalidAddr
@@ -219,6 +303,66 @@ func parseDefaultAddr(b []byte) (Addr, error) {
 	return a, nil
 }
 
+func addrsSpace(as []Addr) int {
+	var l int
+	for _, a := range as {
+		switch a := a.(type) {
+		case *LinkAddr:
+			_, ll := a.lenAndSpace()
+			l += ll
+		case *Inet4Addr:
+			_, ll := a.lenAndSpace()
+			l += ll
+		case *Inet6Addr:
+			_, ll := a.lenAndSpace()
+			l += ll
+		case *DefaultAddr:
+			_, ll := a.lenAndSpace()
+			l += ll
+		}
+	}
+	return l
+}
+
+// marshalAddrs marshals as returns a bitmap indicating which address
+// is stored in b.
+func marshalAddrs(b []byte, as []Addr) (uint, error) {
+	var attrs uint
+	for i, a := range as {
+		switch a := a.(type) {
+		case *LinkAddr:
+			l, err := a.marshal(b)
+			if err != nil {
+				return 0, err
+			}
+			b = b[l:]
+			attrs |= 1 << uint(i)
+		case *Inet4Addr:
+			l, err := a.marshal(b)
+			if err != nil {
+				return 0, err
+			}
+			b = b[l:]
+			attrs |= 1 << uint(i)
+		case *Inet6Addr:
+			l, err := a.marshal(b)
+			if err != nil {
+				return 0, err
+			}
+			b = b[l:]
+			attrs |= 1 << uint(i)
+		case *DefaultAddr:
+			l, err := a.marshal(b)
+			if err != nil {
+				return 0, err
+			}
+			b = b[l:]
+			attrs |= 1 << uint(i)
+		}
+	}
+	return attrs, nil
+}
+
 func parseAddrs(attrs uint, fn func(int, []byte) (int, Addr, error), b []byte) ([]Addr, error) {
 	var as [sysRTAX_MAX]Addr
 	af := int(sysAF_UNSPEC)

+ 8 - 0
route/defs_darwin.go

@@ -13,6 +13,8 @@ package route
 #include <net/if.h>
 #include <net/if_dl.h>
 #include <net/route.h>
+
+#include <netinet/in.h>
 */
 import "C"
 
@@ -23,6 +25,8 @@ const (
 	sysAF_LINK   = C.AF_LINK
 	sysAF_INET6  = C.AF_INET6
 
+	sysSOCK_RAW = C.SOCK_RAW
+
 	sysNET_RT_DUMP    = C.NET_RT_DUMP
 	sysNET_RT_FLAGS   = C.NET_RT_FLAGS
 	sysNET_RT_IFLIST  = C.NET_RT_IFLIST
@@ -103,4 +107,8 @@ const (
 	sizeofRtMsghdrDarwin15  = C.sizeof_struct_rt_msghdr
 	sizeofRtMsghdr2Darwin15 = C.sizeof_struct_rt_msghdr2
 	sizeofRtMetricsDarwin15 = C.sizeof_struct_rt_metrics
+
+	sizeofSockaddrStorage = C.sizeof_struct_sockaddr_storage
+	sizeofSockaddrInet    = C.sizeof_struct_sockaddr_in
+	sizeofSockaddrInet6   = C.sizeof_struct_sockaddr_in6
 )

+ 8 - 0
route/defs_dragonfly.go

@@ -13,6 +13,8 @@ package route
 #include <net/if.h>
 #include <net/if_dl.h>
 #include <net/route.h>
+
+#include <netinet/in.h>
 */
 import "C"
 
@@ -23,6 +25,8 @@ const (
 	sysAF_LINK   = C.AF_LINK
 	sysAF_INET6  = C.AF_INET6
 
+	sysSOCK_RAW = C.SOCK_RAW
+
 	sysNET_RT_DUMP   = C.NET_RT_DUMP
 	sysNET_RT_FLAGS  = C.NET_RT_FLAGS
 	sysNET_RT_IFLIST = C.NET_RT_IFLIST
@@ -102,4 +106,8 @@ const (
 
 	sizeofRtMsghdrDragonFlyBSD4  = C.sizeof_struct_rt_msghdr
 	sizeofRtMetricsDragonFlyBSD4 = C.sizeof_struct_rt_metrics
+
+	sizeofSockaddrStorage = C.sizeof_struct_sockaddr_storage
+	sizeofSockaddrInet    = C.sizeof_struct_sockaddr_in
+	sizeofSockaddrInet6   = C.sizeof_struct_sockaddr_in6
 )

+ 8 - 0
route/defs_freebsd.go

@@ -14,6 +14,8 @@ package route
 #include <net/if_dl.h>
 #include <net/route.h>
 
+#include <netinet/in.h>
+
 struct if_data_freebsd7 {
 	u_char ifi_type;
 	u_char ifi_physical;
@@ -222,6 +224,8 @@ const (
 	sysAF_LINK   = C.AF_LINK
 	sysAF_INET6  = C.AF_INET6
 
+	sysSOCK_RAW = C.SOCK_RAW
+
 	sysNET_RT_DUMP     = C.NET_RT_DUMP
 	sysNET_RT_FLAGS    = C.NET_RT_FLAGS
 	sysNET_RT_IFLIST   = C.NET_RT_IFLIST
@@ -326,4 +330,8 @@ const (
 	sizeofIfDataFreeBSD9Emu  = C.sizeof_struct_if_data_freebsd9
 	sizeofIfDataFreeBSD10Emu = C.sizeof_struct_if_data_freebsd10
 	sizeofIfDataFreeBSD11Emu = C.sizeof_struct_if_data_freebsd11
+
+	sizeofSockaddrStorage = C.sizeof_struct_sockaddr_storage
+	sizeofSockaddrInet    = C.sizeof_struct_sockaddr_in
+	sizeofSockaddrInet6   = C.sizeof_struct_sockaddr_in6
 )

+ 8 - 0
route/defs_netbsd.go

@@ -13,6 +13,8 @@ package route
 #include <net/if.h>
 #include <net/if_dl.h>
 #include <net/route.h>
+
+#include <netinet/in.h>
 */
 import "C"
 
@@ -23,6 +25,8 @@ const (
 	sysAF_LINK   = C.AF_LINK
 	sysAF_INET6  = C.AF_INET6
 
+	sysSOCK_RAW = C.SOCK_RAW
+
 	sysNET_RT_DUMP   = C.NET_RT_DUMP
 	sysNET_RT_FLAGS  = C.NET_RT_FLAGS
 	sysNET_RT_IFLIST = C.NET_RT_IFLIST
@@ -101,4 +105,8 @@ const (
 
 	sizeofRtMsghdrNetBSD7  = C.sizeof_struct_rt_msghdr
 	sizeofRtMetricsNetBSD7 = C.sizeof_struct_rt_metrics
+
+	sizeofSockaddrStorage = C.sizeof_struct_sockaddr_storage
+	sizeofSockaddrInet    = C.sizeof_struct_sockaddr_in
+	sizeofSockaddrInet6   = C.sizeof_struct_sockaddr_in6
 )

+ 12 - 0
route/defs_openbsd.go

@@ -13,6 +13,8 @@ package route
 #include <net/if.h>
 #include <net/if_dl.h>
 #include <net/route.h>
+
+#include <netinet/in.h>
 */
 import "C"
 
@@ -23,6 +25,8 @@ const (
 	sysAF_LINK   = C.AF_LINK
 	sysAF_INET6  = C.AF_INET6
 
+	sysSOCK_RAW = C.SOCK_RAW
+
 	sysNET_RT_DUMP    = C.NET_RT_DUMP
 	sysNET_RT_FLAGS   = C.NET_RT_FLAGS
 	sysNET_RT_IFLIST  = C.NET_RT_IFLIST
@@ -91,3 +95,11 @@ const (
 	sysRTAX_LABEL   = C.RTAX_LABEL
 	sysRTAX_MAX     = C.RTAX_MAX
 )
+
+const (
+	sizeofRtMsghdr = C.sizeof_struct_rt_msghdr
+
+	sizeofSockaddrStorage = C.sizeof_struct_sockaddr_storage
+	sizeofSockaddrInet    = C.sizeof_struct_sockaddr_in
+	sizeofSockaddrInet6   = C.sizeof_struct_sockaddr_in6
+)

+ 2 - 6
route/message.go

@@ -7,9 +7,6 @@
 package route
 
 // A Message represents a routing message.
-//
-// Note: This interface will be changed to support Marshal method in
-// future version.
 type Message interface {
 	// Sys returns operating system-specific information.
 	Sys() []Sys
@@ -52,11 +49,10 @@ func ParseRIB(typ RIBType, b []byte) ([]Message, error) {
 			b = b[l:]
 			continue
 		}
-		mtyp := int(b[3])
-		if fn, ok := parseFns[mtyp]; !ok {
+		if w, ok := wireFormats[int(b[3])]; !ok {
 			nskips++
 		} else {
-			m, err := fn(typ, b)
+			m, err := w.parse(typ, b)
 			if err != nil {
 				return nil, err
 			}

+ 122 - 7
route/message_test.go

@@ -33,11 +33,28 @@ func TestFetchAndParseRIB(t *testing.T) {
 	}
 }
 
+var (
+	rtmonSock int
+	rtmonErr  error
+)
+
+func init() {
+	// We need to keep rtmonSock alive to avoid treading on
+	// recycled socket descriptors.
+	rtmonSock, rtmonErr = syscall.Socket(sysAF_ROUTE, sysSOCK_RAW, sysAF_UNSPEC)
+}
+
+// TestMonitorAndParseRIB leaks a worker goroutine and a socket
+// descriptor but that's intentional.
 func TestMonitorAndParseRIB(t *testing.T) {
 	if testing.Short() || os.Getuid() != 0 {
 		t.Skip("must be root")
 	}
 
+	if rtmonErr != nil {
+		t.Fatal(rtmonErr)
+	}
+
 	// We suppose that using an IPv4 link-local address and the
 	// dot1Q ID for Token Ring and FDDI doesn't harm anyone.
 	pv := &propVirtual{addr: "169.254.0.1", mask: "255.255.255.0"}
@@ -49,16 +66,18 @@ func TestMonitorAndParseRIB(t *testing.T) {
 	}
 	pv.teardown()
 
-	s, err := syscall.Socket(syscall.AF_ROUTE, syscall.SOCK_RAW, syscall.AF_UNSPEC)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer syscall.Close(s)
-
 	go func() {
 		b := make([]byte, os.Getpagesize())
 		for {
-			n, err := syscall.Read(s, b)
+			// There's no easy way to unblock this read
+			// call because the routing message exchange
+			// over routing socket is a connectionless
+			// message-oriented protocol, no control plane
+			// for signaling connectivity, and we cannot
+			// use the net package of standard library due
+			// to the lack of support for routing socket
+			// and circular dependency.
+			n, err := syscall.Read(rtmonSock, b)
 			if err != nil {
 				return
 			}
@@ -116,3 +135,99 @@ func TestParseRIBWithFuzz(t *testing.T) {
 		}
 	}
 }
+
+func TestRouteMessage(t *testing.T) {
+	s, err := syscall.Socket(sysAF_ROUTE, sysSOCK_RAW, sysAF_UNSPEC)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer syscall.Close(s)
+
+	var ms []RouteMessage
+	for _, af := range []int{sysAF_INET, sysAF_INET6} {
+		rs, err := fetchAndParseRIB(af, sysNET_RT_DUMP)
+		if err != nil || len(rs) == 0 {
+			continue
+		}
+		switch af {
+		case sysAF_INET:
+			ms = append(ms, []RouteMessage{
+				{
+					Type: sysRTM_GET,
+					Addrs: []Addr{
+						&Inet4Addr{IP: [4]byte{127, 0, 0, 1}},
+						nil,
+						nil,
+						nil,
+						&LinkAddr{},
+						&Inet4Addr{},
+						nil,
+						&Inet4Addr{},
+					},
+				},
+				{
+					Type: sysRTM_GET,
+					Addrs: []Addr{
+						&Inet4Addr{IP: [4]byte{127, 0, 0, 1}},
+					},
+				},
+			}...)
+		case sysAF_INET6:
+			ms = append(ms, []RouteMessage{
+				{
+					Type: sysRTM_GET,
+					Addrs: []Addr{
+						&Inet6Addr{IP: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}},
+						nil,
+						nil,
+						nil,
+						&LinkAddr{},
+						&Inet6Addr{},
+						nil,
+						&Inet6Addr{},
+					},
+				},
+				{
+					Type: sysRTM_GET,
+					Addrs: []Addr{
+						&Inet6Addr{IP: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}},
+					},
+				},
+			}...)
+		}
+	}
+	for i, m := range ms {
+		m.ID = uintptr(os.Getpid())
+		m.Seq = i + 1
+		wb, err := m.Marshal()
+		if err != nil {
+			t.Fatalf("%v: %v", m, err)
+		}
+		if _, err := syscall.Write(s, wb); err != nil {
+			t.Fatalf("%v: %v", m, err)
+		}
+		rb := make([]byte, os.Getpagesize())
+		n, err := syscall.Read(s, rb)
+		if err != nil {
+			t.Fatalf("%v: %v", m, err)
+		}
+		rms, err := ParseRIB(0, rb[:n])
+		if err != nil {
+			t.Fatalf("%v: %v", m, err)
+		}
+		for _, rm := range rms {
+			err := rm.(*RouteMessage).Err
+			if err != nil {
+				t.Errorf("%v: %v", m, err)
+			}
+		}
+		ss, err := msgs(rms).validate()
+		if err != nil {
+			t.Fatalf("%v: %v", m, err)
+		}
+		for _, s := range ss {
+			t.Log(s)
+		}
+
+	}
+}

+ 54 - 5
route/route.go

@@ -24,21 +24,70 @@ var (
 	errMessageTooShort    = errors.New("message too short")
 	errInvalidMessage     = errors.New("invalid message")
 	errInvalidAddr        = errors.New("invalid address")
+	errShortBuffer        = errors.New("short buffer")
 )
 
 // A RouteMessage represents a message conveying an address prefix, a
 // nexthop address and an output interface.
+//
+// Unlike other messages, this message can be used to query adjacency
+// information for the given address prefix, to add a new route, and
+// to delete or modify the existing route from the routing information
+// base inside the kernel by writing and reading route messages on a
+// routing socket.
+//
+// For the manipulation of routing information, the route message must
+// contain appropriate fields that include:
+//
+//	Version       = <must be specified>
+//	Type          = <must be specified>
+//	Flags         = <must be specified>
+//	Index         = <must be specified if necessary>
+//	ID            = <must be specified>
+//	Seq           = <must be specified>
+//	Addrs         = <must be specified>
+//
+// The Type field specifies a type of manipulation, the Flags field
+// specifies a class of target information and the Addrs field
+// specifies target information like the following:
+//
+//	route.RouteMessage{
+//		Version: RTM_VERSION,
+//		Type: RTM_GET,
+//		Flags: RTF_UP | RTF_HOST,
+//		ID: uintptr(os.Getpid()),
+//		Seq: 1,
+//		Addrs: []route.Addrs{
+//			RTAX_DST: &route.Inet4Addr{ ... },
+//			RTAX_IFP: &route.LinkAddr{ ... },
+//			RTAX_BRD: &route.Inet4Addr{ ... },
+//		},
+//	}
+//
+// The values for the above fields depend on the implementation of
+// each operating system.
+//
+// The Err field on a response message contains an error value on the
+// requested operation. If non-nil, the requested operation is failed.
 type RouteMessage struct {
-	Version int    // message version
-	Type    int    // message type
-	Flags   int    // route flags
-	Index   int    // interface index when atatched
-	Addrs   []Addr // addresses
+	Version int     // message version
+	Type    int     // message type
+	Flags   int     // route flags
+	Index   int     // interface index when atatched
+	ID      uintptr // sender's identifier; usually process ID
+	Seq     int     // sequence number
+	Err     error   // error on requested operation
+	Addrs   []Addr  // addresses
 
 	extOff int    // offset of header extension
 	raw    []byte // raw message
 }
 
+// Marshal returns the binary encoding of m.
+func (m *RouteMessage) Marshal() ([]byte, error) {
+	return m.marshal()
+}
+
 // A RIBType reprensents a type of routing information base.
 type RIBType int
 

+ 36 - 0
route/route_classic.go

@@ -6,6 +6,36 @@
 
 package route
 
+import "syscall"
+
+func (m *RouteMessage) marshal() ([]byte, error) {
+	w, ok := wireFormats[m.Type]
+	if !ok {
+		return nil, errUnsupportedMessage
+	}
+	l := w.bodyOff + addrsSpace(m.Addrs)
+	b := make([]byte, l)
+	nativeEndian.PutUint16(b[:2], uint16(l))
+	if m.Version == 0 {
+		b[2] = sysRTM_VERSION
+	} else {
+		b[2] = byte(m.Version)
+	}
+	b[3] = byte(m.Type)
+	nativeEndian.PutUint32(b[8:12], uint32(m.Flags))
+	nativeEndian.PutUint16(b[4:6], uint16(m.Index))
+	nativeEndian.PutUint32(b[16:20], uint32(m.ID))
+	nativeEndian.PutUint32(b[20:24], uint32(m.Seq))
+	attrs, err := marshalAddrs(b[w.bodyOff:], m.Addrs)
+	if err != nil {
+		return nil, err
+	}
+	if attrs > 0 {
+		nativeEndian.PutUint32(b[12:16], uint32(attrs))
+	}
+	return b, nil
+}
+
 func (w *wireFormat) parseRouteMessage(typ RIBType, b []byte) (Message, error) {
 	if len(b) < w.bodyOff {
 		return nil, errMessageTooShort
@@ -19,9 +49,15 @@ func (w *wireFormat) parseRouteMessage(typ RIBType, b []byte) (Message, error) {
 		Type:    int(b[3]),
 		Flags:   int(nativeEndian.Uint32(b[8:12])),
 		Index:   int(nativeEndian.Uint16(b[4:6])),
+		ID:      uintptr(nativeEndian.Uint32(b[16:20])),
+		Seq:     int(nativeEndian.Uint32(b[20:24])),
 		extOff:  w.extOff,
 		raw:     b[:l],
 	}
+	errno := syscall.Errno(nativeEndian.Uint32(b[28:32]))
+	if errno != 0 {
+		m.Err = errno
+	}
 	var err error
 	m.Addrs, err = parseAddrs(uint(nativeEndian.Uint32(b[12:16])), parseKernelInetAddr, b[w.bodyOff:])
 	if err != nil {

+ 34 - 1
route/route_openbsd.go

@@ -4,8 +4,35 @@
 
 package route
 
+import "syscall"
+
+func (m *RouteMessage) marshal() ([]byte, error) {
+	l := sizeofRtMsghdr + addrsSpace(m.Addrs)
+	b := make([]byte, l)
+	nativeEndian.PutUint16(b[:2], uint16(l))
+	if m.Version == 0 {
+		b[2] = sysRTM_VERSION
+	} else {
+		b[2] = byte(m.Version)
+	}
+	b[3] = byte(m.Type)
+	nativeEndian.PutUint16(b[4:6], uint16(sizeofRtMsghdr))
+	nativeEndian.PutUint32(b[16:20], uint32(m.Flags))
+	nativeEndian.PutUint16(b[6:8], uint16(m.Index))
+	nativeEndian.PutUint32(b[24:28], uint32(m.ID))
+	nativeEndian.PutUint32(b[28:32], uint32(m.Seq))
+	attrs, err := marshalAddrs(b[sizeofRtMsghdr:], m.Addrs)
+	if err != nil {
+		return nil, err
+	}
+	if attrs > 0 {
+		nativeEndian.PutUint32(b[12:16], uint32(attrs))
+	}
+	return b, nil
+}
+
 func (*wireFormat) parseRouteMessage(_ RIBType, b []byte) (Message, error) {
-	if len(b) < 40 {
+	if len(b) < sizeofRtMsghdr {
 		return nil, errMessageTooShort
 	}
 	l := int(nativeEndian.Uint16(b[:2]))
@@ -17,12 +44,18 @@ func (*wireFormat) parseRouteMessage(_ RIBType, b []byte) (Message, error) {
 		Type:    int(b[3]),
 		Flags:   int(nativeEndian.Uint32(b[16:20])),
 		Index:   int(nativeEndian.Uint16(b[6:8])),
+		ID:      uintptr(nativeEndian.Uint32(b[24:28])),
+		Seq:     int(nativeEndian.Uint32(b[28:32])),
 		raw:     b[:l],
 	}
 	ll := int(nativeEndian.Uint16(b[4:6]))
 	if len(b) < ll {
 		return nil, errInvalidMessage
 	}
+	errno := syscall.Errno(nativeEndian.Uint32(b[32:36]))
+	if errno != 0 {
+		m.Err = errno
+	}
 	as, err := parseAddrs(uint(nativeEndian.Uint32(b[12:16])), parseKernelInetAddr, b[ll:])
 	if err != nil {
 		return nil, err

+ 3 - 4
route/sys.go

@@ -11,7 +11,7 @@ import "unsafe"
 var (
 	nativeEndian binaryByteOrder
 	kernelAlign  int
-	parseFns     map[int]parseFn
+	wireFormats  map[int]*wireFormat
 )
 
 func init() {
@@ -22,7 +22,7 @@ func init() {
 	} else {
 		nativeEndian = bigEndian
 	}
-	kernelAlign, parseFns = probeRoutingStack()
+	kernelAlign, wireFormats = probeRoutingStack()
 }
 
 func roundup(l int) int {
@@ -32,9 +32,8 @@ func roundup(l int) int {
 	return (l + kernelAlign - 1) & ^(kernelAlign - 1)
 }
 
-type parseFn func(RIBType, []byte) (Message, error)
-
 type wireFormat struct {
 	extOff  int // offset of header extension
 	bodyOff int // offset of message body
+	parse   func(RIBType, []byte) (Message, error)
 }

+ 26 - 19
route/sys_darwin.go

@@ -49,32 +49,39 @@ func (m *InterfaceMessage) Sys() []Sys {
 	}
 }
 
-func probeRoutingStack() (int, map[int]parseFn) {
+func probeRoutingStack() (int, map[int]*wireFormat) {
 	rtm := &wireFormat{extOff: 36, bodyOff: sizeofRtMsghdrDarwin15}
+	rtm.parse = rtm.parseRouteMessage
 	rtm2 := &wireFormat{extOff: 36, bodyOff: sizeofRtMsghdr2Darwin15}
+	rtm2.parse = rtm2.parseRouteMessage
 	ifm := &wireFormat{extOff: 16, bodyOff: sizeofIfMsghdrDarwin15}
+	ifm.parse = ifm.parseInterfaceMessage
 	ifm2 := &wireFormat{extOff: 32, bodyOff: sizeofIfMsghdr2Darwin15}
+	ifm2.parse = ifm2.parseInterfaceMessage
 	ifam := &wireFormat{extOff: sizeofIfaMsghdrDarwin15, bodyOff: sizeofIfaMsghdrDarwin15}
+	ifam.parse = ifam.parseInterfaceAddrMessage
 	ifmam := &wireFormat{extOff: sizeofIfmaMsghdrDarwin15, bodyOff: sizeofIfmaMsghdrDarwin15}
+	ifmam.parse = ifmam.parseInterfaceMulticastAddrMessage
 	ifmam2 := &wireFormat{extOff: sizeofIfmaMsghdr2Darwin15, bodyOff: sizeofIfmaMsghdr2Darwin15}
+	ifmam2.parse = ifmam2.parseInterfaceMulticastAddrMessage
 	// Darwin kernels require 32-bit aligned access to routing facilities.
-	return 4, map[int]parseFn{
-		sysRTM_ADD:       rtm.parseRouteMessage,
-		sysRTM_DELETE:    rtm.parseRouteMessage,
-		sysRTM_CHANGE:    rtm.parseRouteMessage,
-		sysRTM_GET:       rtm.parseRouteMessage,
-		sysRTM_LOSING:    rtm.parseRouteMessage,
-		sysRTM_REDIRECT:  rtm.parseRouteMessage,
-		sysRTM_MISS:      rtm.parseRouteMessage,
-		sysRTM_LOCK:      rtm.parseRouteMessage,
-		sysRTM_RESOLVE:   rtm.parseRouteMessage,
-		sysRTM_NEWADDR:   ifam.parseInterfaceAddrMessage,
-		sysRTM_DELADDR:   ifam.parseInterfaceAddrMessage,
-		sysRTM_IFINFO:    ifm.parseInterfaceMessage,
-		sysRTM_NEWMADDR:  ifmam.parseInterfaceMulticastAddrMessage,
-		sysRTM_DELMADDR:  ifmam.parseInterfaceMulticastAddrMessage,
-		sysRTM_IFINFO2:   ifm2.parseInterfaceMessage,
-		sysRTM_NEWMADDR2: ifmam2.parseInterfaceMulticastAddrMessage,
-		sysRTM_GET2:      rtm2.parseRouteMessage,
+	return 4, map[int]*wireFormat{
+		sysRTM_ADD:       rtm,
+		sysRTM_DELETE:    rtm,
+		sysRTM_CHANGE:    rtm,
+		sysRTM_GET:       rtm,
+		sysRTM_LOSING:    rtm,
+		sysRTM_REDIRECT:  rtm,
+		sysRTM_MISS:      rtm,
+		sysRTM_LOCK:      rtm,
+		sysRTM_RESOLVE:   rtm,
+		sysRTM_NEWADDR:   ifam,
+		sysRTM_DELADDR:   ifam,
+		sysRTM_IFINFO:    ifm,
+		sysRTM_NEWMADDR:  ifmam,
+		sysRTM_DELMADDR:  ifmam,
+		sysRTM_IFINFO2:   ifm2,
+		sysRTM_NEWMADDR2: ifmam2,
+		sysRTM_GET2:      rtm2,
 	}
 }

+ 22 - 17
route/sys_dragonfly.go

@@ -44,28 +44,33 @@ func (m *InterfaceMessage) Sys() []Sys {
 	}
 }
 
-func probeRoutingStack() (int, map[int]parseFn) {
+func probeRoutingStack() (int, map[int]*wireFormat) {
 	var p uintptr
 	rtm := &wireFormat{extOff: 40, bodyOff: sizeofRtMsghdrDragonFlyBSD4}
+	rtm.parse = rtm.parseRouteMessage
 	ifm := &wireFormat{extOff: 16, bodyOff: sizeofIfMsghdrDragonFlyBSD4}
+	ifm.parse = ifm.parseInterfaceMessage
 	ifam := &wireFormat{extOff: sizeofIfaMsghdrDragonFlyBSD4, bodyOff: sizeofIfaMsghdrDragonFlyBSD4}
+	ifam.parse = ifam.parseInterfaceAddrMessage
 	ifmam := &wireFormat{extOff: sizeofIfmaMsghdrDragonFlyBSD4, bodyOff: sizeofIfmaMsghdrDragonFlyBSD4}
+	ifmam.parse = ifmam.parseInterfaceMulticastAddrMessage
 	ifanm := &wireFormat{extOff: sizeofIfAnnouncemsghdrDragonFlyBSD4, bodyOff: sizeofIfAnnouncemsghdrDragonFlyBSD4}
-	return int(unsafe.Sizeof(p)), map[int]parseFn{
-		sysRTM_ADD:        rtm.parseRouteMessage,
-		sysRTM_DELETE:     rtm.parseRouteMessage,
-		sysRTM_CHANGE:     rtm.parseRouteMessage,
-		sysRTM_GET:        rtm.parseRouteMessage,
-		sysRTM_LOSING:     rtm.parseRouteMessage,
-		sysRTM_REDIRECT:   rtm.parseRouteMessage,
-		sysRTM_MISS:       rtm.parseRouteMessage,
-		sysRTM_LOCK:       rtm.parseRouteMessage,
-		sysRTM_RESOLVE:    rtm.parseRouteMessage,
-		sysRTM_NEWADDR:    ifam.parseInterfaceAddrMessage,
-		sysRTM_DELADDR:    ifam.parseInterfaceAddrMessage,
-		sysRTM_IFINFO:     ifm.parseInterfaceMessage,
-		sysRTM_NEWMADDR:   ifmam.parseInterfaceMulticastAddrMessage,
-		sysRTM_DELMADDR:   ifmam.parseInterfaceMulticastAddrMessage,
-		sysRTM_IFANNOUNCE: ifanm.parseInterfaceAnnounceMessage,
+	ifanm.parse = ifanm.parseInterfaceAnnounceMessage
+	return int(unsafe.Sizeof(p)), map[int]*wireFormat{
+		sysRTM_ADD:        rtm,
+		sysRTM_DELETE:     rtm,
+		sysRTM_CHANGE:     rtm,
+		sysRTM_GET:        rtm,
+		sysRTM_LOSING:     rtm,
+		sysRTM_REDIRECT:   rtm,
+		sysRTM_MISS:       rtm,
+		sysRTM_LOCK:       rtm,
+		sysRTM_RESOLVE:    rtm,
+		sysRTM_NEWADDR:    ifam,
+		sysRTM_DELADDR:    ifam,
+		sysRTM_IFINFO:     ifm,
+		sysRTM_NEWMADDR:   ifmam,
+		sysRTM_DELMADDR:   ifmam,
+		sysRTM_IFANNOUNCE: ifanm,
 	}
 }

+ 22 - 17
route/sys_freebsd.go

@@ -54,7 +54,7 @@ func (m *InterfaceMessage) Sys() []Sys {
 	}
 }
 
-func probeRoutingStack() (int, map[int]parseFn) {
+func probeRoutingStack() (int, map[int]*wireFormat) {
 	var p uintptr
 	wordSize := int(unsafe.Sizeof(p))
 	align := int(unsafe.Sizeof(p))
@@ -130,21 +130,26 @@ func probeRoutingStack() (int, map[int]parseFn) {
 			ifm.bodyOff = sizeofIfMsghdrFreeBSD11
 		}
 	}
-	return align, map[int]parseFn{
-		sysRTM_ADD:        rtm.parseRouteMessage,
-		sysRTM_DELETE:     rtm.parseRouteMessage,
-		sysRTM_CHANGE:     rtm.parseRouteMessage,
-		sysRTM_GET:        rtm.parseRouteMessage,
-		sysRTM_LOSING:     rtm.parseRouteMessage,
-		sysRTM_REDIRECT:   rtm.parseRouteMessage,
-		sysRTM_MISS:       rtm.parseRouteMessage,
-		sysRTM_LOCK:       rtm.parseRouteMessage,
-		sysRTM_RESOLVE:    rtm.parseRouteMessage,
-		sysRTM_NEWADDR:    ifam.parseInterfaceAddrMessage,
-		sysRTM_DELADDR:    ifam.parseInterfaceAddrMessage,
-		sysRTM_IFINFO:     ifm.parseInterfaceMessage,
-		sysRTM_NEWMADDR:   ifmam.parseInterfaceMulticastAddrMessage,
-		sysRTM_DELMADDR:   ifmam.parseInterfaceMulticastAddrMessage,
-		sysRTM_IFANNOUNCE: ifanm.parseInterfaceAnnounceMessage,
+	rtm.parse = rtm.parseRouteMessage
+	ifm.parse = ifm.parseInterfaceMessage
+	ifam.parse = ifam.parseInterfaceAddrMessage
+	ifmam.parse = ifmam.parseInterfaceMulticastAddrMessage
+	ifanm.parse = ifanm.parseInterfaceAnnounceMessage
+	return align, map[int]*wireFormat{
+		sysRTM_ADD:        rtm,
+		sysRTM_DELETE:     rtm,
+		sysRTM_CHANGE:     rtm,
+		sysRTM_GET:        rtm,
+		sysRTM_LOSING:     rtm,
+		sysRTM_REDIRECT:   rtm,
+		sysRTM_MISS:       rtm,
+		sysRTM_LOCK:       rtm,
+		sysRTM_RESOLVE:    rtm,
+		sysRTM_NEWADDR:    ifam,
+		sysRTM_DELADDR:    ifam,
+		sysRTM_IFINFO:     ifm,
+		sysRTM_NEWMADDR:   ifmam,
+		sysRTM_DELMADDR:   ifmam,
+		sysRTM_IFANNOUNCE: ifanm,
 	}
 }

+ 19 - 15
route/sys_netbsd.go

@@ -42,26 +42,30 @@ func (m *InterfaceMessage) Sys() []Sys {
 	}
 }
 
-func probeRoutingStack() (int, map[int]parseFn) {
+func probeRoutingStack() (int, map[int]*wireFormat) {
 	rtm := &wireFormat{extOff: 40, bodyOff: sizeofRtMsghdrNetBSD7}
+	rtm.parse = rtm.parseRouteMessage
 	ifm := &wireFormat{extOff: 16, bodyOff: sizeofIfMsghdrNetBSD7}
+	ifm.parse = ifm.parseInterfaceMessage
 	ifam := &wireFormat{extOff: sizeofIfaMsghdrNetBSD7, bodyOff: sizeofIfaMsghdrNetBSD7}
+	ifam.parse = ifam.parseInterfaceAddrMessage
 	ifanm := &wireFormat{extOff: sizeofIfAnnouncemsghdrNetBSD7, bodyOff: sizeofIfAnnouncemsghdrNetBSD7}
+	ifanm.parse = ifanm.parseInterfaceAnnounceMessage
 	// NetBSD 6 and above kernels require 64-bit aligned access to
 	// routing facilities.
-	return 8, map[int]parseFn{
-		sysRTM_ADD:        rtm.parseRouteMessage,
-		sysRTM_DELETE:     rtm.parseRouteMessage,
-		sysRTM_CHANGE:     rtm.parseRouteMessage,
-		sysRTM_GET:        rtm.parseRouteMessage,
-		sysRTM_LOSING:     rtm.parseRouteMessage,
-		sysRTM_REDIRECT:   rtm.parseRouteMessage,
-		sysRTM_MISS:       rtm.parseRouteMessage,
-		sysRTM_LOCK:       rtm.parseRouteMessage,
-		sysRTM_RESOLVE:    rtm.parseRouteMessage,
-		sysRTM_NEWADDR:    ifam.parseInterfaceAddrMessage,
-		sysRTM_DELADDR:    ifam.parseInterfaceAddrMessage,
-		sysRTM_IFANNOUNCE: ifanm.parseInterfaceAnnounceMessage,
-		sysRTM_IFINFO:     ifm.parseInterfaceMessage,
+	return 8, map[int]*wireFormat{
+		sysRTM_ADD:        rtm,
+		sysRTM_DELETE:     rtm,
+		sysRTM_CHANGE:     rtm,
+		sysRTM_GET:        rtm,
+		sysRTM_LOSING:     rtm,
+		sysRTM_REDIRECT:   rtm,
+		sysRTM_MISS:       rtm,
+		sysRTM_LOCK:       rtm,
+		sysRTM_RESOLVE:    rtm,
+		sysRTM_NEWADDR:    ifam,
+		sysRTM_DELADDR:    ifam,
+		sysRTM_IFANNOUNCE: ifanm,
+		sysRTM_IFINFO:     ifm,
 	}
 }

+ 23 - 16
route/sys_openbsd.go

@@ -51,22 +51,29 @@ func (m *InterfaceMessage) Sys() []Sys {
 	}
 }
 
-func probeRoutingStack() (int, map[int]parseFn) {
+func probeRoutingStack() (int, map[int]*wireFormat) {
 	var p uintptr
-	nooff := &wireFormat{extOff: -1, bodyOff: -1}
-	return int(unsafe.Sizeof(p)), map[int]parseFn{
-		sysRTM_ADD:        nooff.parseRouteMessage,
-		sysRTM_DELETE:     nooff.parseRouteMessage,
-		sysRTM_CHANGE:     nooff.parseRouteMessage,
-		sysRTM_GET:        nooff.parseRouteMessage,
-		sysRTM_LOSING:     nooff.parseRouteMessage,
-		sysRTM_REDIRECT:   nooff.parseRouteMessage,
-		sysRTM_MISS:       nooff.parseRouteMessage,
-		sysRTM_LOCK:       nooff.parseRouteMessage,
-		sysRTM_RESOLVE:    nooff.parseRouteMessage,
-		sysRTM_NEWADDR:    nooff.parseInterfaceAddrMessage,
-		sysRTM_DELADDR:    nooff.parseInterfaceAddrMessage,
-		sysRTM_IFINFO:     nooff.parseInterfaceMessage,
-		sysRTM_IFANNOUNCE: nooff.parseInterfaceAnnounceMessage,
+	rtm := &wireFormat{extOff: -1, bodyOff: -1}
+	rtm.parse = rtm.parseRouteMessage
+	ifm := &wireFormat{extOff: -1, bodyOff: -1}
+	ifm.parse = ifm.parseInterfaceMessage
+	ifam := &wireFormat{extOff: -1, bodyOff: -1}
+	ifam.parse = ifam.parseInterfaceAddrMessage
+	ifanm := &wireFormat{extOff: -1, bodyOff: -1}
+	ifanm.parse = ifanm.parseInterfaceAnnounceMessage
+	return int(unsafe.Sizeof(p)), map[int]*wireFormat{
+		sysRTM_ADD:        rtm,
+		sysRTM_DELETE:     rtm,
+		sysRTM_CHANGE:     rtm,
+		sysRTM_GET:        rtm,
+		sysRTM_LOSING:     rtm,
+		sysRTM_REDIRECT:   rtm,
+		sysRTM_MISS:       rtm,
+		sysRTM_LOCK:       rtm,
+		sysRTM_RESOLVE:    rtm,
+		sysRTM_NEWADDR:    ifam,
+		sysRTM_DELADDR:    ifam,
+		sysRTM_IFINFO:     ifm,
+		sysRTM_IFANNOUNCE: ifanm,
 	}
 }

+ 6 - 0
route/zsys_darwin.go

@@ -10,6 +10,8 @@ const (
 	sysAF_LINK   = 0x12
 	sysAF_INET6  = 0x1e
 
+	sysSOCK_RAW = 0x3
+
 	sysNET_RT_DUMP    = 0x1
 	sysNET_RT_FLAGS   = 0x2
 	sysNET_RT_IFLIST  = 0x3
@@ -90,4 +92,8 @@ const (
 	sizeofRtMsghdrDarwin15  = 0x5c
 	sizeofRtMsghdr2Darwin15 = 0x5c
 	sizeofRtMetricsDarwin15 = 0x38
+
+	sizeofSockaddrStorage = 0x80
+	sizeofSockaddrInet    = 0x10
+	sizeofSockaddrInet6   = 0x1c
 )

+ 6 - 0
route/zsys_dragonfly.go

@@ -10,6 +10,8 @@ const (
 	sysAF_LINK   = 0x12
 	sysAF_INET6  = 0x1c
 
+	sysSOCK_RAW = 0x3
+
 	sysNET_RT_DUMP   = 0x1
 	sysNET_RT_FLAGS  = 0x2
 	sysNET_RT_IFLIST = 0x3
@@ -89,4 +91,8 @@ const (
 
 	sizeofRtMsghdrDragonFlyBSD4  = 0x98
 	sizeofRtMetricsDragonFlyBSD4 = 0x70
+
+	sizeofSockaddrStorage = 0x80
+	sizeofSockaddrInet    = 0x10
+	sizeofSockaddrInet6   = 0x1c
 )

+ 6 - 0
route/zsys_freebsd_386.go

@@ -10,6 +10,8 @@ const (
 	sysAF_LINK   = 0x12
 	sysAF_INET6  = 0x1c
 
+	sysSOCK_RAW = 0x3
+
 	sysNET_RT_DUMP     = 0x1
 	sysNET_RT_FLAGS    = 0x2
 	sysNET_RT_IFLIST   = 0x3
@@ -117,4 +119,8 @@ const (
 	sizeofIfDataFreeBSD9Emu  = 0x98
 	sizeofIfDataFreeBSD10Emu = 0x98
 	sizeofIfDataFreeBSD11Emu = 0x98
+
+	sizeofSockaddrStorage = 0x80
+	sizeofSockaddrInet    = 0x10
+	sizeofSockaddrInet6   = 0x1c
 )

+ 6 - 0
route/zsys_freebsd_amd64.go

@@ -10,6 +10,8 @@ const (
 	sysAF_LINK   = 0x12
 	sysAF_INET6  = 0x1c
 
+	sysSOCK_RAW = 0x3
+
 	sysNET_RT_DUMP     = 0x1
 	sysNET_RT_FLAGS    = 0x2
 	sysNET_RT_IFLIST   = 0x3
@@ -114,4 +116,8 @@ const (
 	sizeofIfDataFreeBSD9Emu  = 0x98
 	sizeofIfDataFreeBSD10Emu = 0x98
 	sizeofIfDataFreeBSD11Emu = 0x98
+
+	sizeofSockaddrStorage = 0x80
+	sizeofSockaddrInet    = 0x10
+	sizeofSockaddrInet6   = 0x1c
 )

+ 6 - 0
route/zsys_freebsd_arm.go

@@ -10,6 +10,8 @@ const (
 	sysAF_LINK   = 0x12
 	sysAF_INET6  = 0x1c
 
+	sysSOCK_RAW = 0x3
+
 	sysNET_RT_DUMP     = 0x1
 	sysNET_RT_FLAGS    = 0x2
 	sysNET_RT_IFLIST   = 0x3
@@ -114,4 +116,8 @@ const (
 	sizeofIfDataFreeBSD9Emu  = 0x60
 	sizeofIfDataFreeBSD10Emu = 0x60
 	sizeofIfDataFreeBSD11Emu = 0x98
+
+	sizeofSockaddrStorage = 0x80
+	sizeofSockaddrInet    = 0x10
+	sizeofSockaddrInet6   = 0x1c
 )

+ 6 - 0
route/zsys_netbsd.go

@@ -10,6 +10,8 @@ const (
 	sysAF_LINK   = 0x12
 	sysAF_INET6  = 0x18
 
+	sysSOCK_RAW = 0x3
+
 	sysNET_RT_DUMP   = 0x1
 	sysNET_RT_FLAGS  = 0x2
 	sysNET_RT_IFLIST = 0x5
@@ -88,4 +90,8 @@ const (
 
 	sizeofRtMsghdrNetBSD7  = 0x78
 	sizeofRtMetricsNetBSD7 = 0x50
+
+	sizeofSockaddrStorage = 0x80
+	sizeofSockaddrInet    = 0x10
+	sizeofSockaddrInet6   = 0x1c
 )

+ 10 - 0
route/zsys_openbsd.go

@@ -10,6 +10,8 @@ const (
 	sysAF_LINK   = 0x12
 	sysAF_INET6  = 0x18
 
+	sysSOCK_RAW = 0x3
+
 	sysNET_RT_DUMP    = 0x1
 	sysNET_RT_FLAGS   = 0x2
 	sysNET_RT_IFLIST  = 0x3
@@ -78,3 +80,11 @@ const (
 	sysRTAX_LABEL   = 0xa
 	sysRTAX_MAX     = 0xb
 )
+
+const (
+	sizeofRtMsghdr = 0x60
+
+	sizeofSockaddrStorage = 0x100
+	sizeofSockaddrInet    = 0x10
+	sizeofSockaddrInet6   = 0x1c
+)