support MPLS

$ ip -M route add 100 dev eth0
$ ip -M route add 100 as to 200/300 dev eth0
$ ip -M route add 100 nexthop dev eth0 as to 200 \
                      nexthop dev eth1 as to 300
$ ip route add 10.10.0.0/24 encap mpls 200/300 dev eth0
$ ip route add 10.0.0.0/24 nexthop encap mpls 200 dev eth0 \
                           nexthop encap mpls 300 dev eth1

Signed-off-by: ISHIDA Wataru <ishida.wataru@lab.ntt.co.jp>
This commit is contained in:
ISHIDA Wataru 2017-02-19 03:22:09 +00:00 committed by Vish Ishaya
parent dedc638a06
commit fe3b5664d2
9 changed files with 423 additions and 38 deletions

View File

@ -4,7 +4,8 @@ import "github.com/vishvananda/netlink/nl"
// Family type definitions // Family type definitions
const ( const (
FAMILY_ALL = nl.FAMILY_ALL FAMILY_ALL = nl.FAMILY_ALL
FAMILY_V4 = nl.FAMILY_V4 FAMILY_V4 = nl.FAMILY_V4
FAMILY_V6 = nl.FAMILY_V6 FAMILY_V6 = nl.FAMILY_V6
FAMILY_MPLS = nl.FAMILY_MPLS
) )

View File

@ -36,3 +36,23 @@ func setUpNetlinkTest(t *testing.T) tearDownNetlinkTest {
runtime.UnlockOSThread() runtime.UnlockOSThread()
} }
} }
func setUpMPLSNetlinkTest(t *testing.T) tearDownNetlinkTest {
if _, err := os.Stat("/proc/sys/net/mpls/platform_labels"); err != nil {
msg := "Skipped test because it requires MPLS support."
log.Printf(msg)
t.Skip(msg)
}
f := setUpNetlinkTest(t)
setUpF := func(path, value string) {
file, err := os.Create(path)
defer file.Close()
if err != nil {
t.Fatalf("Failed to open %s: %s", path, err)
}
file.WriteString(value)
}
setUpF("/proc/sys/net/mpls/platform_labels", "1024")
setUpF("/proc/sys/net/mpls/conf/lo/input", "1")
return f
}

36
nl/mpls_linux.go Normal file
View File

@ -0,0 +1,36 @@
package nl
import "encoding/binary"
const (
MPLS_LS_LABEL_SHIFT = 12
MPLS_LS_S_SHIFT = 8
)
func EncodeMPLSStack(labels ...int) []byte {
b := make([]byte, 4*len(labels))
for idx, label := range labels {
l := label << MPLS_LS_LABEL_SHIFT
if idx == len(labels)-1 {
l |= 1 << MPLS_LS_S_SHIFT
}
binary.BigEndian.PutUint32(b[idx*4:], uint32(l))
}
return b
}
func DecodeMPLSStack(buf []byte) []int {
if len(buf)%4 != 0 {
return nil
}
stack := make([]int, 0, len(buf)/4)
for len(buf) > 0 {
l := binary.BigEndian.Uint32(buf[:4])
buf = buf[4:]
stack = append(stack, int(l)>>MPLS_LS_LABEL_SHIFT)
if (l>>MPLS_LS_S_SHIFT)&1 > 0 {
break
}
}
return stack
}

View File

@ -17,9 +17,10 @@ import (
const ( const (
// Family type definitions // Family type definitions
FAMILY_ALL = syscall.AF_UNSPEC FAMILY_ALL = syscall.AF_UNSPEC
FAMILY_V4 = syscall.AF_INET FAMILY_V4 = syscall.AF_INET
FAMILY_V6 = syscall.AF_INET6 FAMILY_V6 = syscall.AF_INET6
FAMILY_MPLS = AF_MPLS
) )
// SupportedNlFamilies contains the list of netlink families this netlink package supports // SupportedNlFamilies contains the list of netlink families this netlink package supports

View File

@ -43,12 +43,38 @@ func (msg *RtMsg) Serialize() []byte {
type RtNexthop struct { type RtNexthop struct {
syscall.RtNexthop syscall.RtNexthop
Children []NetlinkRequestData
} }
func DeserializeRtNexthop(b []byte) *RtNexthop { func DeserializeRtNexthop(b []byte) *RtNexthop {
return (*RtNexthop)(unsafe.Pointer(&b[0:syscall.SizeofRtNexthop][0])) return (*RtNexthop)(unsafe.Pointer(&b[0:syscall.SizeofRtNexthop][0]))
} }
func (msg *RtNexthop) Serialize() []byte { func (msg *RtNexthop) Len() int {
return (*(*[syscall.SizeofRtNexthop]byte)(unsafe.Pointer(msg)))[:] if len(msg.Children) == 0 {
return syscall.SizeofRtNexthop
}
l := 0
for _, child := range msg.Children {
l += rtaAlignOf(child.Len())
}
l += syscall.SizeofRtNexthop
return rtaAlignOf(l)
}
func (msg *RtNexthop) Serialize() []byte {
length := msg.Len()
msg.RtNexthop.Len = uint16(length)
buf := make([]byte, length)
copy(buf, (*(*[syscall.SizeofRtNexthop]byte)(unsafe.Pointer(msg)))[:])
next := rtaAlignOf(syscall.SizeofRtNexthop)
if len(msg.Children) > 0 {
for _, child := range msg.Children {
childBuf := child.Serialize()
copy(buf[next:], childBuf)
next += rtaAlignOf(len(childBuf))
}
}
return buf
} }

View File

@ -41,3 +41,28 @@ const (
SOCK_DIAG_BY_FAMILY = 20 /* linux.sock_diag.h */ SOCK_DIAG_BY_FAMILY = 20 /* linux.sock_diag.h */
TCPDIAG_NOCOOKIE = 0xFFFFFFFF /* TCPDIAG_NOCOOKIE in net/ipv4/tcp_diag.h*/ TCPDIAG_NOCOOKIE = 0xFFFFFFFF /* TCPDIAG_NOCOOKIE in net/ipv4/tcp_diag.h*/
) )
const (
AF_MPLS = 28
)
const (
RTA_NEWDST = 0x13
RTA_ENCAP_TYPE = 0x15
RTA_ENCAP = 0x16
)
// RTA_ENCAP subtype
const (
MPLS_IPTUNNEL_UNSPEC = iota
MPLS_IPTUNNEL_DST
)
// light weight tunnel encap types
const (
LWTUNNEL_ENCAP_NONE = iota
LWTUNNEL_ENCAP_MPLS
LWTUNNEL_ENCAP_IP
LWTUNNEL_ENCAP_ILA
LWTUNNEL_ENCAP_IP6
)

View File

@ -3,6 +3,7 @@ package netlink
import ( import (
"fmt" "fmt"
"net" "net"
"strings"
) )
// Scope is an enum representing a route scope. // Scope is an enum representing a route scope.
@ -10,6 +11,20 @@ type Scope uint8
type NextHopFlag int type NextHopFlag int
type Destination interface {
Family() int
Decode([]byte) error
Encode() ([]byte, error)
String() string
}
type Encap interface {
Type() int
Decode([]byte) error
Encode() ([]byte, error)
String() string
}
// Route represents a netlink route. // Route represents a netlink route.
type Route struct { type Route struct {
LinkIndex int LinkIndex int
@ -25,15 +40,36 @@ type Route struct {
Type int Type int
Tos int Tos int
Flags int Flags int
MPLSDst *int
NewDst Destination
Encap Encap
} }
func (r Route) String() string { func (r Route) String() string {
if len(r.MultiPath) > 0 { elems := []string{}
return fmt.Sprintf("{Dst: %s Src: %s Gw: %s Flags: %s Table: %d}", r.Dst, if len(r.MultiPath) == 0 {
r.Src, r.MultiPath, r.ListFlags(), r.Table) elems = append(elems, fmt.Sprintf("Ifindex: %d", r.LinkIndex))
} }
return fmt.Sprintf("{Ifindex: %d Dst: %s Src: %s Gw: %s Flags: %s Table: %d}", r.LinkIndex, r.Dst, if r.MPLSDst != nil {
r.Src, r.Gw, r.ListFlags(), r.Table) elems = append(elems, fmt.Sprintf("Dst: %d", r.MPLSDst))
} else {
elems = append(elems, fmt.Sprintf("Dst: %s", r.Dst))
}
if r.NewDst != nil {
elems = append(elems, fmt.Sprintf("NewDst: %s", r.NewDst))
}
if r.Encap != nil {
elems = append(elems, fmt.Sprintf("Encap: %s", r.Encap))
}
elems = append(elems, fmt.Sprintf("Src: %s", r.Src))
if len(r.MultiPath) > 0 {
elems = append(elems, fmt.Sprintf("Gw: %s", r.MultiPath))
} else {
elems = append(elems, fmt.Sprintf("Gw: %s", r.Gw))
}
elems = append(elems, fmt.Sprintf("Flags: %s", r.ListFlags()))
elems = append(elems, fmt.Sprintf("Table: %d", r.Table))
return fmt.Sprintf("{%s}", strings.Join(elems, " "))
} }
func (r *Route) SetFlag(flag NextHopFlag) { func (r *Route) SetFlag(flag NextHopFlag) {
@ -60,8 +96,21 @@ type NexthopInfo struct {
Hops int Hops int
Gw net.IP Gw net.IP
Flags int Flags int
NewDst Destination
Encap Encap
} }
func (n *NexthopInfo) String() string { func (n *NexthopInfo) String() string {
return fmt.Sprintf("{Ifindex: %d Weight: %d Gw: %s Flags: %s}", n.LinkIndex, n.Hops+1, n.Gw, n.ListFlags()) elems := []string{}
elems = append(elems, fmt.Sprintf("Ifindex: %d", n.LinkIndex))
if n.NewDst != nil {
elems = append(elems, fmt.Sprintf("NewDst: %s", n.NewDst))
}
if n.Encap != nil {
elems = append(elems, fmt.Sprintf("Encap: %s", n.Encap))
}
elems = append(elems, fmt.Sprintf("Weight: %d", n.Hops+1))
elems = append(elems, fmt.Sprintf("Gw: %d", n.Gw))
elems = append(elems, fmt.Sprintf("Flags: %s", n.ListFlags()))
return fmt.Sprintf("{%s}", strings.Join(elems, " "))
} }

View File

@ -3,6 +3,7 @@ package netlink
import ( import (
"fmt" "fmt"
"net" "net"
"strings"
"syscall" "syscall"
"github.com/vishvananda/netlink/nl" "github.com/vishvananda/netlink/nl"
@ -60,6 +61,74 @@ func (n *NexthopInfo) ListFlags() []string {
return listFlags(n.Flags) return listFlags(n.Flags)
} }
type MPLSDestination struct {
Labels []int
}
func (d *MPLSDestination) Family() int {
return nl.FAMILY_MPLS
}
func (d *MPLSDestination) Decode(buf []byte) error {
d.Labels = nl.DecodeMPLSStack(buf)
return nil
}
func (d *MPLSDestination) Encode() ([]byte, error) {
return nl.EncodeMPLSStack(d.Labels...), nil
}
func (d *MPLSDestination) String() string {
s := make([]string, 0, len(d.Labels))
for _, l := range d.Labels {
s = append(s, fmt.Sprintf("%d", l))
}
return strings.Join(s, "/")
}
type MPLSEncap struct {
Labels []int
}
func (e *MPLSEncap) Type() int {
return nl.LWTUNNEL_ENCAP_MPLS
}
func (e *MPLSEncap) Decode(buf []byte) error {
if len(buf) < 4 {
return fmt.Errorf("Lack of bytes")
}
native := nl.NativeEndian()
l := native.Uint16(buf)
if len(buf) < int(l) {
return fmt.Errorf("Lack of bytes")
}
buf = buf[:l]
typ := native.Uint16(buf[2:])
if typ != nl.MPLS_IPTUNNEL_DST {
return fmt.Errorf("Unknown MPLS Encap Type: %d", typ)
}
e.Labels = nl.DecodeMPLSStack(buf[4:])
return nil
}
func (e *MPLSEncap) Encode() ([]byte, error) {
s := nl.EncodeMPLSStack(e.Labels...)
native := nl.NativeEndian()
hdr := make([]byte, 4)
native.PutUint16(hdr, uint16(len(s)+4))
native.PutUint16(hdr[2:], nl.MPLS_IPTUNNEL_DST)
return append(hdr, s...), nil
}
func (e *MPLSEncap) String() string {
s := make([]string, 0, len(e.Labels))
for _, l := range e.Labels {
s = append(s, fmt.Sprintf("%d", l))
}
return strings.Join(s, "/")
}
// RouteAdd will add a route to the system. // RouteAdd will add a route to the system.
// Equivalent to: `ip route add $route` // Equivalent to: `ip route add $route`
func RouteAdd(route *Route) error { func RouteAdd(route *Route) error {
@ -102,7 +171,7 @@ func (h *Handle) RouteDel(route *Route) error {
} }
func (h *Handle) routeHandle(route *Route, req *nl.NetlinkRequest, msg *nl.RtMsg) error { func (h *Handle) routeHandle(route *Route, req *nl.NetlinkRequest, msg *nl.RtMsg) error {
if (route.Dst == nil || route.Dst.IP == nil) && route.Src == nil && route.Gw == nil { if (route.Dst == nil || route.Dst.IP == nil) && route.Src == nil && route.Gw == nil && route.MPLSDst == nil {
return fmt.Errorf("one of Dst.IP, Src, or Gw must not be nil") return fmt.Errorf("one of Dst.IP, Src, or Gw must not be nil")
} }
@ -121,6 +190,33 @@ func (h *Handle) routeHandle(route *Route, req *nl.NetlinkRequest, msg *nl.RtMsg
dstData = route.Dst.IP.To16() dstData = route.Dst.IP.To16()
} }
rtAttrs = append(rtAttrs, nl.NewRtAttr(syscall.RTA_DST, dstData)) rtAttrs = append(rtAttrs, nl.NewRtAttr(syscall.RTA_DST, dstData))
} else if route.MPLSDst != nil {
family = nl.FAMILY_MPLS
msg.Dst_len = uint8(20)
msg.Type = syscall.RTN_UNICAST
rtAttrs = append(rtAttrs, nl.NewRtAttr(syscall.RTA_DST, nl.EncodeMPLSStack(*route.MPLSDst)))
}
if route.NewDst != nil {
if family != -1 && family != route.NewDst.Family() {
return fmt.Errorf("new destination and destination are not the same address family")
}
buf, err := route.NewDst.Encode()
if err != nil {
return err
}
rtAttrs = append(rtAttrs, nl.NewRtAttr(nl.RTA_NEWDST, buf))
}
if route.Encap != nil {
buf := make([]byte, 2)
native.PutUint16(buf, uint16(route.Encap.Type()))
rtAttrs = append(rtAttrs, nl.NewRtAttr(nl.RTA_ENCAP_TYPE, buf))
buf, err := route.Encap.Encode()
if err != nil {
return err
}
rtAttrs = append(rtAttrs, nl.NewRtAttr(nl.RTA_ENCAP, buf))
} }
if route.Src != nil { if route.Src != nil {
@ -161,27 +257,43 @@ func (h *Handle) routeHandle(route *Route, req *nl.NetlinkRequest, msg *nl.RtMsg
RtNexthop: syscall.RtNexthop{ RtNexthop: syscall.RtNexthop{
Hops: uint8(nh.Hops), Hops: uint8(nh.Hops),
Ifindex: int32(nh.LinkIndex), Ifindex: int32(nh.LinkIndex),
Len: uint16(syscall.SizeofRtNexthop),
Flags: uint8(nh.Flags), Flags: uint8(nh.Flags),
}, },
} }
var gwData []byte children := []nl.NetlinkRequestData{}
if nh.Gw != nil { if nh.Gw != nil {
gwFamily := nl.GetIPFamily(nh.Gw) gwFamily := nl.GetIPFamily(nh.Gw)
if family != -1 && family != gwFamily { if family != -1 && family != gwFamily {
return fmt.Errorf("gateway, source, and destination ip are not the same IP family") return fmt.Errorf("gateway, source, and destination ip are not the same IP family")
} }
var gw *nl.RtAttr
if gwFamily == FAMILY_V4 { if gwFamily == FAMILY_V4 {
gw = nl.NewRtAttr(syscall.RTA_GATEWAY, []byte(nh.Gw.To4())) children = append(children, nl.NewRtAttr(syscall.RTA_GATEWAY, []byte(nh.Gw.To4())))
} else { } else {
gw = nl.NewRtAttr(syscall.RTA_GATEWAY, []byte(nh.Gw.To16())) children = append(children, nl.NewRtAttr(syscall.RTA_GATEWAY, []byte(nh.Gw.To16())))
} }
gwData = gw.Serialize()
rtnh.Len += uint16(len(gwData))
} }
if nh.NewDst != nil {
if family != -1 && family != nh.NewDst.Family() {
return fmt.Errorf("new destination and destination are not the same address family")
}
buf, err := nh.NewDst.Encode()
if err != nil {
return err
}
children = append(children, nl.NewRtAttr(nl.RTA_NEWDST, buf))
}
if nh.Encap != nil {
buf := make([]byte, 2)
native.PutUint16(buf, uint16(nh.Encap.Type()))
rtAttrs = append(rtAttrs, nl.NewRtAttr(nl.RTA_ENCAP_TYPE, buf))
buf, err := nh.Encap.Encode()
if err != nil {
return err
}
children = append(children, nl.NewRtAttr(nl.RTA_ENCAP, buf))
}
rtnh.Children = children
buf = append(buf, rtnh.Serialize()...) buf = append(buf, rtnh.Serialize()...)
buf = append(buf, gwData...)
} }
rtAttrs = append(rtAttrs, nl.NewRtAttr(syscall.RTA_MULTIPATH, buf)) rtAttrs = append(rtAttrs, nl.NewRtAttr(syscall.RTA_MULTIPATH, buf))
} }
@ -308,18 +420,20 @@ func (h *Handle) RouteListFiltered(family int, filter *Route, filterMask uint64)
case filterMask&RT_FILTER_SRC != 0 && !route.Src.Equal(filter.Src): case filterMask&RT_FILTER_SRC != 0 && !route.Src.Equal(filter.Src):
continue continue
case filterMask&RT_FILTER_DST != 0: case filterMask&RT_FILTER_DST != 0:
if filter.Dst == nil { if filter.MPLSDst == nil || route.MPLSDst == nil || (*filter.MPLSDst) != (*route.MPLSDst) {
if route.Dst != nil { if filter.Dst == nil {
continue if route.Dst != nil {
} continue
} else { }
if route.Dst == nil { } else {
continue if route.Dst == nil {
} continue
aMaskLen, aMaskBits := route.Dst.Mask.Size() }
bMaskLen, bMaskBits := filter.Dst.Mask.Size() aMaskLen, aMaskBits := route.Dst.Mask.Size()
if !(route.Dst.IP.Equal(filter.Dst.IP) && aMaskLen == bMaskLen && aMaskBits == bMaskBits) { bMaskLen, bMaskBits := filter.Dst.Mask.Size()
continue if !(route.Dst.IP.Equal(filter.Dst.IP) && aMaskLen == bMaskLen && aMaskBits == bMaskBits) {
continue
}
} }
} }
} }
@ -346,6 +460,7 @@ func deserializeRoute(m []byte) (Route, error) {
} }
native := nl.NativeEndian() native := nl.NativeEndian()
var encap, encapType syscall.NetlinkRouteAttr
for _, attr := range attrs { for _, attr := range attrs {
switch attr.Attr.Type { switch attr.Attr.Type {
case syscall.RTA_GATEWAY: case syscall.RTA_GATEWAY:
@ -353,9 +468,17 @@ func deserializeRoute(m []byte) (Route, error) {
case syscall.RTA_PREFSRC: case syscall.RTA_PREFSRC:
route.Src = net.IP(attr.Value) route.Src = net.IP(attr.Value)
case syscall.RTA_DST: case syscall.RTA_DST:
route.Dst = &net.IPNet{ if msg.Family == nl.FAMILY_MPLS {
IP: attr.Value, stack := nl.DecodeMPLSStack(attr.Value)
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attr.Value)), if len(stack) == 0 || len(stack) > 1 {
return route, fmt.Errorf("invalid MPLS RTA_DST")
}
route.MPLSDst = &stack[0]
} else {
route.Dst = &net.IPNet{
IP: attr.Value,
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attr.Value)),
}
} }
case syscall.RTA_OIF: case syscall.RTA_OIF:
route.LinkIndex = int(native.Uint32(attr.Value[0:4])) route.LinkIndex = int(native.Uint32(attr.Value[0:4]))
@ -383,12 +506,41 @@ func deserializeRoute(m []byte) (Route, error) {
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
var encap, encapType syscall.NetlinkRouteAttr
for _, attr := range attrs { for _, attr := range attrs {
switch attr.Attr.Type { switch attr.Attr.Type {
case syscall.RTA_GATEWAY: case syscall.RTA_GATEWAY:
info.Gw = net.IP(attr.Value) info.Gw = net.IP(attr.Value)
case nl.RTA_NEWDST:
var d Destination
switch msg.Family {
case nl.FAMILY_MPLS:
d = &MPLSDestination{}
}
if err := d.Decode(attr.Value); err != nil {
return nil, nil, err
}
info.NewDst = d
case nl.RTA_ENCAP_TYPE:
encapType = attr
case nl.RTA_ENCAP:
encap = attr
} }
} }
if len(encap.Value) != 0 && len(encapType.Value) != 0 {
typ := int(native.Uint16(encapType.Value[0:2]))
var e Encap
switch typ {
case nl.LWTUNNEL_ENCAP_MPLS:
e = &MPLSEncap{}
if err := e.Decode(encap.Value); err != nil {
return nil, nil, err
}
}
info.Encap = e
}
return info, value[int(nh.RtNexthop.Len):], nil return info, value[int(nh.RtNexthop.Len):], nil
} }
rest := attr.Value rest := attr.Value
@ -400,8 +552,36 @@ func deserializeRoute(m []byte) (Route, error) {
route.MultiPath = append(route.MultiPath, info) route.MultiPath = append(route.MultiPath, info)
rest = buf rest = buf
} }
case nl.RTA_NEWDST:
var d Destination
switch msg.Family {
case nl.FAMILY_MPLS:
d = &MPLSDestination{}
}
if err := d.Decode(attr.Value); err != nil {
return route, err
}
route.NewDst = d
case nl.RTA_ENCAP_TYPE:
encapType = attr
case nl.RTA_ENCAP:
encap = attr
} }
} }
if len(encap.Value) != 0 && len(encapType.Value) != 0 {
typ := int(native.Uint16(encapType.Value[0:2]))
var e Encap
switch typ {
case nl.LWTUNNEL_ENCAP_MPLS:
e = &MPLSEncap{}
if err := e.Decode(encap.Value); err != nil {
return route, err
}
}
route.Encap = e
}
return route, nil return route, nil
} }

View File

@ -451,3 +451,50 @@ func TestFilterDefaultRoute(t *testing.T) {
} }
} }
func TestMPLSRouteAddDel(t *testing.T) {
tearDown := setUpMPLSNetlinkTest(t)
defer tearDown()
// get loopback interface
link, err := LinkByName("lo")
if err != nil {
t.Fatal(err)
}
// bring the interface up
if err := LinkSetUp(link); err != nil {
t.Fatal(err)
}
mplsDst := 100
route := Route{
LinkIndex: link.Attrs().Index,
MPLSDst: &mplsDst,
NewDst: &MPLSDestination{
Labels: []int{200, 300},
},
}
if err := RouteAdd(&route); err != nil {
t.Fatal(err)
}
routes, err := RouteList(link, FAMILY_MPLS)
if err != nil {
t.Fatal(err)
}
if len(routes) != 1 {
t.Fatal("Route not added properly")
}
if err := RouteDel(&route); err != nil {
t.Fatal(err)
}
routes, err = RouteList(link, FAMILY_MPLS)
if err != nil {
t.Fatal(err)
}
if len(routes) != 0 {
t.Fatal("Route not removed properly")
}
}