From 16769db0024183962b509594b3f9034118615c48 Mon Sep 17 00:00:00 2001 From: Kentaro Ebisawa Date: Tue, 1 May 2018 13:11:23 +0900 Subject: [PATCH] Support LWTUNNEL_ENCAP_SEG6_LOCAL (including tests) --- nl/seg6_linux.go | 43 ++++++++++ nl/seg6local_linux.go | 76 +++++++++++++++++ nl/syscall.go | 1 + route_linux.go | 188 ++++++++++++++++++++++++++++++++++++++++++ route_test.go | 178 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 486 insertions(+) create mode 100644 nl/seg6local_linux.go diff --git a/nl/seg6_linux.go b/nl/seg6_linux.go index b3425f6..d11fea7 100644 --- a/nl/seg6_linux.go +++ b/nl/seg6_linux.go @@ -99,6 +99,49 @@ func DecodeSEG6Encap(buf []byte) (int, []net.IP, error) { return mode, srh.Segments, nil } +func DecodeSEG6Srh(buf []byte) ([]net.IP, error) { + native := NativeEndian() + srh := IPv6SrHdr{ + nextHdr: buf[0], + hdrLen: buf[1], + routingType: buf[2], + segmentsLeft: buf[3], + firstSegment: buf[4], + flags: buf[5], + reserved: native.Uint16(buf[6:8]), + } + buf = buf[8:] + if len(buf)%16 != 0 { + err := fmt.Errorf("DecodeSEG6Srh: error parsing Segment List (buf len: %d)\n", len(buf)) + return nil, err + } + for len(buf) > 0 { + srh.Segments = append(srh.Segments, net.IP(buf[:16])) + buf = buf[16:] + } + return srh.Segments, nil +} +func EncodeSEG6Srh(segments []net.IP) ([]byte, error) { + nsegs := len(segments) // nsegs: number of segments + if nsegs == 0 { + return nil, errors.New("EncodeSEG6Srh: No Segments\n") + } + b := make([]byte, 8, 8+len(segments)*16) + native := NativeEndian() + b[0] = 0 // srh.nextHdr (0 when calling netlink) + b[1] = uint8(16 * nsegs >> 3) // srh.hdrLen (in 8-octets unit) + b[2] = IPV6_SRCRT_TYPE_4 // srh.routingType (assigned by IANA) + b[3] = uint8(nsegs - 1) // srh.segmentsLeft + b[4] = uint8(nsegs - 1) // srh.firstSegment + b[5] = 0 // srh.flags (SR6_FLAG1_HMAC for srh_hmac) + // srh.reserved: Defined as "Tag" in draft-ietf-6man-segment-routing-header-07 + native.PutUint16(b[6:], 0) // srh.reserved + for _, netIP := range segments { + b = append(b, netIP...) // srh.Segments + } + return b, nil +} + // Helper functions func SEG6EncapModeString(mode int) string { switch mode { diff --git a/nl/seg6local_linux.go b/nl/seg6local_linux.go new file mode 100644 index 0000000..1500177 --- /dev/null +++ b/nl/seg6local_linux.go @@ -0,0 +1,76 @@ +package nl + +import () + +// seg6local parameters +const ( + SEG6_LOCAL_UNSPEC = iota + SEG6_LOCAL_ACTION + SEG6_LOCAL_SRH + SEG6_LOCAL_TABLE + SEG6_LOCAL_NH4 + SEG6_LOCAL_NH6 + SEG6_LOCAL_IIF + SEG6_LOCAL_OIF + __SEG6_LOCAL_MAX +) +const ( + SEG6_LOCAL_MAX = __SEG6_LOCAL_MAX +) + +// seg6local actions +const ( + SEG6_LOCAL_ACTION_END = iota + 1 // 1 + SEG6_LOCAL_ACTION_END_X // 2 + SEG6_LOCAL_ACTION_END_T // 3 + SEG6_LOCAL_ACTION_END_DX2 // 4 + SEG6_LOCAL_ACTION_END_DX6 // 5 + SEG6_LOCAL_ACTION_END_DX4 // 6 + SEG6_LOCAL_ACTION_END_DT6 // 7 + SEG6_LOCAL_ACTION_END_DT4 // 8 + SEG6_LOCAL_ACTION_END_B6 // 9 + SEG6_LOCAL_ACTION_END_B6_ENCAPS // 10 + SEG6_LOCAL_ACTION_END_BM // 11 + SEG6_LOCAL_ACTION_END_S // 12 + SEG6_LOCAL_ACTION_END_AS // 13 + SEG6_LOCAL_ACTION_END_AM // 14 + __SEG6_LOCAL_ACTION_MAX +) +const ( + SEG6_LOCAL_ACTION_MAX = __SEG6_LOCAL_ACTION_MAX - 1 +) + +// Helper functions +func SEG6LocalActionString(action int) string { + switch action { + case SEG6_LOCAL_ACTION_END: + return "End" + case SEG6_LOCAL_ACTION_END_X: + return "End.X" + case SEG6_LOCAL_ACTION_END_T: + return "End.T" + case SEG6_LOCAL_ACTION_END_DX2: + return "End.DX2" + case SEG6_LOCAL_ACTION_END_DX6: + return "End.DX6" + case SEG6_LOCAL_ACTION_END_DX4: + return "End.DX4" + case SEG6_LOCAL_ACTION_END_DT6: + return "End.DT6" + case SEG6_LOCAL_ACTION_END_DT4: + return "End.DT4" + case SEG6_LOCAL_ACTION_END_B6: + return "End.B6" + case SEG6_LOCAL_ACTION_END_B6_ENCAPS: + return "End.B6.Encaps" + case SEG6_LOCAL_ACTION_END_BM: + return "End.BM" + case SEG6_LOCAL_ACTION_END_S: + return "End.S" + case SEG6_LOCAL_ACTION_END_AS: + return "End.AS" + case SEG6_LOCAL_ACTION_END_AM: + return "End.AM" + } + return "unknown" +} diff --git a/nl/syscall.go b/nl/syscall.go index fc631e0..0ff1bba 100644 --- a/nl/syscall.go +++ b/nl/syscall.go @@ -67,6 +67,7 @@ const ( LWTUNNEL_ENCAP_IP6 LWTUNNEL_ENCAP_SEG6 LWTUNNEL_ENCAP_BPF + LWTUNNEL_ENCAP_SEG6_LOCAL ) // routing header types diff --git a/route_linux.go b/route_linux.go index 3f85671..7233549 100644 --- a/route_linux.go +++ b/route_linux.go @@ -207,6 +207,7 @@ func (e *SEG6Encap) Decode(buf []byte) error { } buf = buf[:l] // make sure buf size upper limit is Length typ := native.Uint16(buf[2:]) + // LWTUNNEL_ENCAP_SEG6 has only one attr type SEG6_IPTUNNEL_SRH if typ != nl.SEG6_IPTUNNEL_SRH { return fmt.Errorf("unknown SEG6 Type: %d", typ) } @@ -259,6 +260,188 @@ func (e *SEG6Encap) Equal(x Encap) bool { return true } +// SEG6Local definitions +type SEG6LocalEncap struct { + Flags [nl.SEG6_LOCAL_MAX]bool + Action int + Segments []net.IP // from SRH in seg6_local_lwt + Table int // table id for End.T and End.DT6 + InAddr net.IP + In6Addr net.IP + Iif int + Oif int +} + +func (e *SEG6LocalEncap) Type() int { + return nl.LWTUNNEL_ENCAP_SEG6_LOCAL +} +func (e *SEG6LocalEncap) Decode(buf []byte) error { + attrs, err := nl.ParseRouteAttr(buf) + if err != nil { + return err + } + native := nl.NativeEndian() + for _, attr := range attrs { + switch attr.Attr.Type { + case nl.SEG6_LOCAL_ACTION: + e.Action = int(native.Uint32(attr.Value[0:4])) + e.Flags[nl.SEG6_LOCAL_ACTION] = true + case nl.SEG6_LOCAL_SRH: + e.Segments, err = nl.DecodeSEG6Srh(attr.Value[:]) + e.Flags[nl.SEG6_LOCAL_SRH] = true + case nl.SEG6_LOCAL_TABLE: + e.Table = int(native.Uint32(attr.Value[0:4])) + e.Flags[nl.SEG6_LOCAL_TABLE] = true + case nl.SEG6_LOCAL_NH4: + e.InAddr = net.IP(attr.Value[0:4]) + e.Flags[nl.SEG6_LOCAL_NH4] = true + case nl.SEG6_LOCAL_NH6: + e.In6Addr = net.IP(attr.Value[0:16]) + e.Flags[nl.SEG6_LOCAL_NH6] = true + case nl.SEG6_LOCAL_IIF: + e.Iif = int(native.Uint32(attr.Value[0:4])) + e.Flags[nl.SEG6_LOCAL_IIF] = true + case nl.SEG6_LOCAL_OIF: + e.Oif = int(native.Uint32(attr.Value[0:4])) + e.Flags[nl.SEG6_LOCAL_OIF] = true + } + } + return err +} +func (e *SEG6LocalEncap) Encode() ([]byte, error) { + var err error + native := nl.NativeEndian() + res := make([]byte, 8) + native.PutUint16(res, 8) // length + native.PutUint16(res[2:], nl.SEG6_LOCAL_ACTION) + native.PutUint32(res[4:], uint32(e.Action)) + if e.Flags[nl.SEG6_LOCAL_SRH] { + srh, err := nl.EncodeSEG6Srh(e.Segments) + if err != nil { + return nil, err + } + attr := make([]byte, 4) + native.PutUint16(attr, uint16(len(srh)+4)) + native.PutUint16(attr[2:], nl.SEG6_LOCAL_SRH) + attr = append(attr, srh...) + res = append(res, attr...) + } + if e.Flags[nl.SEG6_LOCAL_TABLE] { + attr := make([]byte, 8) + native.PutUint16(attr, 8) + native.PutUint16(attr[2:], nl.SEG6_LOCAL_TABLE) + native.PutUint32(attr[4:], uint32(e.Table)) + res = append(res, attr...) + } + if e.Flags[nl.SEG6_LOCAL_NH4] { + attr := make([]byte, 4) + native.PutUint16(attr, 8) + native.PutUint16(attr[2:], nl.SEG6_LOCAL_NH4) + ipv4 := e.InAddr.To4() + if ipv4 == nil { + err = fmt.Errorf("SEG6_LOCAL_NH4 has invalid IPv4 address") + return nil, err + } + attr = append(attr, ipv4...) + res = append(res, attr...) + } + if e.Flags[nl.SEG6_LOCAL_NH6] { + attr := make([]byte, 4) + native.PutUint16(attr, 20) + native.PutUint16(attr[2:], nl.SEG6_LOCAL_NH6) + attr = append(attr, e.In6Addr...) + res = append(res, attr...) + } + if e.Flags[nl.SEG6_LOCAL_IIF] { + attr := make([]byte, 8) + native.PutUint16(attr, 8) + native.PutUint16(attr[2:], nl.SEG6_LOCAL_IIF) + native.PutUint32(attr[4:], uint32(e.Iif)) + res = append(res, attr...) + } + if e.Flags[nl.SEG6_LOCAL_OIF] { + attr := make([]byte, 8) + native.PutUint16(attr, 8) + native.PutUint16(attr[2:], nl.SEG6_LOCAL_OIF) + native.PutUint32(attr[4:], uint32(e.Oif)) + res = append(res, attr...) + } + return res, err +} +func (e *SEG6LocalEncap) String() string { + strs := make([]string, 0, nl.SEG6_LOCAL_MAX) + strs = append(strs, fmt.Sprintf("action %s", nl.SEG6LocalActionString(e.Action))) + + if e.Flags[nl.SEG6_LOCAL_TABLE] { + strs = append(strs, fmt.Sprintf("table %d", e.Table)) + } + if e.Flags[nl.SEG6_LOCAL_NH4] { + strs = append(strs, fmt.Sprintf("nh4 %s", e.InAddr)) + } + if e.Flags[nl.SEG6_LOCAL_NH6] { + strs = append(strs, fmt.Sprintf("nh6 %s", e.In6Addr)) + } + if e.Flags[nl.SEG6_LOCAL_IIF] { + link, err := LinkByIndex(e.Iif) + if err != nil { + strs = append(strs, fmt.Sprintf("iif %d", e.Iif)) + } else { + strs = append(strs, fmt.Sprintf("iif %s", link.Attrs().Name)) + } + } + if e.Flags[nl.SEG6_LOCAL_OIF] { + link, err := LinkByIndex(e.Oif) + if err != nil { + strs = append(strs, fmt.Sprintf("oif %d", e.Oif)) + } else { + strs = append(strs, fmt.Sprintf("oif %s", link.Attrs().Name)) + } + } + if e.Flags[nl.SEG6_LOCAL_SRH] { + segs := make([]string, 0, len(e.Segments)) + //append segment backwards (from n to 0) since seg#0 is the last segment. + for i := len(e.Segments); i > 0; i-- { + segs = append(segs, fmt.Sprintf("%s", e.Segments[i-1])) + } + strs = append(strs, fmt.Sprintf("segs %d [ %s ]", len(e.Segments), strings.Join(segs, " "))) + } + return strings.Join(strs, " ") +} +func (e *SEG6LocalEncap) Equal(x Encap) bool { + o, ok := x.(*SEG6LocalEncap) + if !ok { + return false + } + if e == o { + return true + } + if e == nil || o == nil { + return false + } + // compare all arrays first + for i := range e.Flags { + if e.Flags[i] != o.Flags[i] { + return false + } + } + if len(e.Segments) != len(o.Segments) { + return false + } + for i := range e.Segments { + if !e.Segments[i].Equal(o.Segments[i]) { + return false + } + } + // compare values + if !e.InAddr.Equal(o.InAddr) || !e.In6Addr.Equal(o.In6Addr) { + return false + } + if e.Action != o.Action || e.Table != o.Table || e.Iif != o.Iif || e.Oif != o.Oif { + return false + } + return true +} + // RouteAdd will add a route to the system. // Equivalent to: `ip route add $route` func RouteAdd(route *Route) error { @@ -734,6 +917,11 @@ func deserializeRoute(m []byte) (Route, error) { if err := e.Decode(encap.Value); err != nil { return route, err } + case nl.LWTUNNEL_ENCAP_SEG6_LOCAL: + e = &SEG6LocalEncap{} + if err := e.Decode(encap.Value); err != nil { + return route, err + } } route.Encap = e } diff --git a/route_test.go b/route_test.go index 4cdf71c..d8ca9af 100644 --- a/route_test.go +++ b/route_test.go @@ -992,6 +992,105 @@ func TestIPNetEqual(t *testing.T) { } } +func TestSEG6LocalEqual(t *testing.T) { + // Different attributes exists in different Actions. For example, Action + // SEG6_LOCAL_ACTION_END_X has In6Addr, SEG6_LOCAL_ACTION_END_T has Table etc. + segs := []net.IP{net.ParseIP("fc00:a000::11")} + // set flags for each actions. + var flags_end [nl.SEG6_LOCAL_MAX]bool + flags_end[nl.SEG6_LOCAL_ACTION] = true + var flags_end_x [nl.SEG6_LOCAL_MAX]bool + flags_end_x[nl.SEG6_LOCAL_ACTION] = true + flags_end_x[nl.SEG6_LOCAL_NH6] = true + var flags_end_t [nl.SEG6_LOCAL_MAX]bool + flags_end_t[nl.SEG6_LOCAL_ACTION] = true + flags_end_t[nl.SEG6_LOCAL_TABLE] = true + var flags_end_dx2 [nl.SEG6_LOCAL_MAX]bool + flags_end_dx2[nl.SEG6_LOCAL_ACTION] = true + flags_end_dx2[nl.SEG6_LOCAL_OIF] = true + var flags_end_dx6 [nl.SEG6_LOCAL_MAX]bool + flags_end_dx6[nl.SEG6_LOCAL_ACTION] = true + flags_end_dx6[nl.SEG6_LOCAL_NH6] = true + var flags_end_dx4 [nl.SEG6_LOCAL_MAX]bool + flags_end_dx4[nl.SEG6_LOCAL_ACTION] = true + flags_end_dx4[nl.SEG6_LOCAL_NH4] = true + var flags_end_dt6 [nl.SEG6_LOCAL_MAX]bool + flags_end_dt6[nl.SEG6_LOCAL_ACTION] = true + flags_end_dt6[nl.SEG6_LOCAL_TABLE] = true + var flags_end_dt4 [nl.SEG6_LOCAL_MAX]bool + flags_end_dt4[nl.SEG6_LOCAL_ACTION] = true + flags_end_dt4[nl.SEG6_LOCAL_TABLE] = true + var flags_end_b6 [nl.SEG6_LOCAL_MAX]bool + flags_end_b6[nl.SEG6_LOCAL_ACTION] = true + flags_end_b6[nl.SEG6_LOCAL_SRH] = true + var flags_end_b6_encaps [nl.SEG6_LOCAL_MAX]bool + flags_end_b6_encaps[nl.SEG6_LOCAL_ACTION] = true + flags_end_b6_encaps[nl.SEG6_LOCAL_SRH] = true + + cases := []SEG6LocalEncap{ + { + Flags: flags_end, + Action: nl.SEG6_LOCAL_ACTION_END, + }, + { + Flags: flags_end_x, + Action: nl.SEG6_LOCAL_ACTION_END_X, + In6Addr: net.ParseIP("2001:db8::1"), + }, + { + Flags: flags_end_t, + Action: nl.SEG6_LOCAL_ACTION_END_T, + Table: 10, + }, + { + Flags: flags_end_dx2, + Action: nl.SEG6_LOCAL_ACTION_END_DX2, + Oif: 20, + }, + { + Flags: flags_end_dx6, + Action: nl.SEG6_LOCAL_ACTION_END_DX6, + In6Addr: net.ParseIP("2001:db8::1"), + }, + { + Flags: flags_end_dx4, + Action: nl.SEG6_LOCAL_ACTION_END_DX4, + InAddr: net.IPv4(192, 168, 10, 10), + }, + { + Flags: flags_end_dt6, + Action: nl.SEG6_LOCAL_ACTION_END_DT6, + Table: 30, + }, + { + Flags: flags_end_dt4, + Action: nl.SEG6_LOCAL_ACTION_END_DT4, + Table: 40, + }, + { + Flags: flags_end_b6, + Action: nl.SEG6_LOCAL_ACTION_END_B6, + Segments: segs, + }, + { + Flags: flags_end_b6_encaps, + Action: nl.SEG6_LOCAL_ACTION_END_B6_ENCAPS, + Segments: segs, + }, + } + for i1 := range cases { + for i2 := range cases { + got := cases[i1].Equal(&cases[i2]) + expected := i1 == i2 + if got != expected { + t.Errorf("Equal(%v,%v) == %s but expected %s", + cases[i1], cases[i2], + strconv.FormatBool(got), + strconv.FormatBool(expected)) + } + } + } +} func TestSEG6RouteAddDel(t *testing.T) { // add/del routes with LWTUNNEL_SEG6 to/from loopback interface. // Test both seg6 modes: encap (IPv4) & inline (IPv6). @@ -1078,6 +1177,85 @@ func TestSEG6RouteAddDel(t *testing.T) { } } +// add/del routes with LWTUNNEL_ENCAP_SEG6_LOCAL to/from dummy interface. +func TestSEG6LocalRoute6AddDel(t *testing.T) { + minKernelRequired(t, 4, 14) + tearDown := setUpSEG6NetlinkTest(t) + defer tearDown() + + // create dummy interface + // IPv6 route added to loopback interface will be unreachable + la := NewLinkAttrs() + la.Name = "dummy_route6" + la.TxQLen = 1500 + dummy := &Dummy{LinkAttrs: la} + if err := LinkAdd(dummy); err != nil { + t.Fatal(err) + } + // get dummy interface and bring it up + link, err := LinkByName("dummy_route6") + if err != nil { + t.Fatal(err) + } + if err := LinkSetUp(link); err != nil { + t.Fatal(err) + } + + dst1 := &net.IPNet{ + IP: net.ParseIP("2001:db8::1"), + Mask: net.CIDRMask(128, 128), + } + + // Create Route including Action SEG6_LOCAL_ACTION_END_B6. + // Could be any Action but thought better to have seg list. + var s1 []net.IP + s1 = append(s1, net.ParseIP("fc00:a000::12")) + s1 = append(s1, net.ParseIP("fc00:a000::11")) + var flags_end_b6_encaps [nl.SEG6_LOCAL_MAX]bool + flags_end_b6_encaps[nl.SEG6_LOCAL_ACTION] = true + flags_end_b6_encaps[nl.SEG6_LOCAL_SRH] = true + e1 := &SEG6LocalEncap{ + Flags: flags_end_b6_encaps, + Action: nl.SEG6_LOCAL_ACTION_END_B6, + Segments: s1, + } + route1 := Route{LinkIndex: link.Attrs().Index, Dst: dst1, Encap: e1} + + // Add SEG6Local routes + if err := RouteAdd(&route1); err != nil { + t.Fatal(err) + } + + // typically one route (fe80::/64) will be created when dummy_route6 is created. + // Thus you cannot use RouteList() to find the route entry just added. + // Lookup route and confirm it's SEG6Local route just added. + routesFound, err := RouteGet(dst1.IP) + if err != nil { + t.Fatal(err) + } + if len(routesFound) != 1 { // should only find 1 route entry + t.Fatal("SEG6Local route not added correctly") + } + if !e1.Equal(routesFound[0].Encap) { + t.Fatal("Encap does not match the original SEG6LocalEncap") + } + + // Del SEG6Local routes + if err := RouteDel(&route1); err != nil { + t.Fatal(err) + } + // Confirm route is deleted. + routesFound, err = RouteGet(dst1.IP) + if err == nil { + t.Fatal("SEG6Local route still exists.") + } + + // cleanup dummy interface created for the test + if err := LinkDel(link); err != nil { + t.Fatal(err) + } +} + func TestMTURouteAddDel(t *testing.T) { _, err := RouteList(nil, FAMILY_V4) if err != nil {