mirror of
https://github.com/vishvananda/netlink.git
synced 2026-04-22 23:27:13 +08:00
6e61cd407d
Add a basic support of Linux's ip nexthop equivalent. In this PR, I specifically focused on implementing a minimal feature to accomplish IPv4 prefix with IPv6 (link-local) nexthop which is used by various implementation like FRR to support technique called BGP Unnumbered. The summary of the new features are: - Introduce a low level primitive for nexthop in the nl package - Introduce NexthopAdd/Del/List/Replace APIs (supports NHA_ID/BLACKHOLE/GATEWAY, and protocol field) - Introduce NHID field to the Route object which allows attaching nexthop to routes. Signed-off-by: Yutaro Hayakawa <yutaro.hayakawa@isovalent.com> === Squashed Commits === nl: Fix some wrong error and done message handling The current logic of parsing ERROR and DONE message is, first reads error field and when NLM_F_ACK_TLVS exists, tries to read the original request header, payload of the request, and extended ACK. We have three issues here: 1. The existence of the original request header is not indicated by NLM_F_ACK_TLVS flag. At least the original request header always exists. 2. We are missing the check for NLM_F_CAPPED flag. When the flag exists, the payload of the request doesn't exist. In that case, we shouldn't try to skip the payload. Otherwise, we may end up with the out-of-range read. 3. NLMSG_DONE doesn't contain the original request, so we shouldn't apply original request parsing logic to it. In this commit, we fix these issues by: 1. We first check the existence of the NLM_F_CAPPED. When it exists, only skip the original request header. Otherwise, skip the payload as well. Don't apply this logic to the DONE message. 2. After that, check the existence of the NLM_F_ACK_TLVS. When it exists, try to read extended ACK for both of DONE and ERROR messages. Otherwise, don't. Ref: https://docs.kernel.org/userspace-api/netlink/intro.html#netlink-message-types Signed-off-by: Yutaro Hayakawa <yutaro.hayakawa@isovalent.com> nexthop: Add a low-level API for the nexthop Preparation for the support of the nexthop object. Signed-off-by: Yutaro Hayakawa <yutaro.hayakawa@isovalent.com> Add basic operation for nexthop Add a basic support of the Linux's nexthop object (ip nexthop XXX). This commit aims to introduce a basic operations (add, list, del) with minimal attributes. Further features can be added later incrementally. Signed-off-by: Yutaro Hayakawa <yutaro.hayakawa@isovalent.com> nexthop: Support NHA_OIF It can be used for expressing direct nexthop on specific link. Signed-off-by: Yutaro Hayakawa <yutaro.hayakawa@isovalent.com> nexthop: Support NHA_GATEWAY It can express an IP nexthop. A unique use case we can accomplish by this is attaching IPv6 nexthop to the routes with an IPv4 prefix which we cannot do with the existing `ip route` equivalents. Signed-off-by: Yutaro Hayakawa <yutaro.hayakawa@isovalent.com> nexthop: Support protocol Allow setting protocol for nexthop. Signed-off-by: Yutaro Hayakawa <yutaro.hayakawa@isovalent.com> route: Support RTA_NHID Support attaching nexthop object to route object via NHID field. Signed-off-by: Yutaro Hayakawa <yutaro.hayakawa@isovalent.com> nexthop: Add Replace operation support Add `ip nexthop replace` equivalent Signed-off-by: Yutaro Hayakawa <yutaro.hayakawa@isovalent.com>
2828 lines
61 KiB
Go
2828 lines
61 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
package netlink
|
|
|
|
import (
|
|
"net"
|
|
"runtime"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/vishvananda/netlink/nl"
|
|
"github.com/vishvananda/netns"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func TestRouteAddDel(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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)
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
|
|
ip := net.IPv4(127, 1, 1, 1)
|
|
route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err := RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("Route not added properly")
|
|
}
|
|
|
|
routes, err = RouteList(nil, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("Route not listed properly")
|
|
}
|
|
|
|
dstIP := net.IPv4(192, 168, 0, 42)
|
|
routeToDstIP, err := RouteGet(dstIP)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(routeToDstIP) == 0 {
|
|
t.Fatal("Default route not present")
|
|
}
|
|
if err := RouteDel(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 0 {
|
|
t.Fatal("Route not removed properly")
|
|
}
|
|
|
|
// add default route test
|
|
// equiv: default dev lo
|
|
_, defaultDst, _ := net.ParseCIDR("0.0.0.0/0")
|
|
route = Route{Dst: defaultDst, LinkIndex: link.Attrs().Index}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("Dev default route not listed properly")
|
|
}
|
|
if err := RouteDel(&routes[0]); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 0 {
|
|
t.Fatal("Dev default route not removed properly")
|
|
}
|
|
|
|
// equiv: blackhole default
|
|
route = Route{Dst: defaultDst, Type: unix.RTN_BLACKHOLE, Family: FAMILY_V4}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(nil, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Logf("%+v", routes)
|
|
|
|
if len(routes) != 1 {
|
|
t.Fatal("Blackhole default route not listed properly")
|
|
}
|
|
|
|
if err := RouteDel(&routes[0]); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(nil, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 0 {
|
|
t.Fatal("Blackhole default route not removed properly")
|
|
}
|
|
|
|
// equiv: prohibit default
|
|
route = Route{Dst: defaultDst, Type: unix.RTN_PROHIBIT}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(nil, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("Prohibit default route not listed properly")
|
|
}
|
|
|
|
if err := RouteDel(&routes[0]); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(nil, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 0 {
|
|
t.Fatal("Prohibit default route not removed properly")
|
|
}
|
|
}
|
|
|
|
func TestRoute6AddDel(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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
|
|
link, err := LinkByName("dummy_route6")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// bring the interface up
|
|
if err := LinkSetUp(link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// remember number of routes before adding
|
|
// typically one route (fe80::/64) will be created when dummy_route6 is created
|
|
routes, err := RouteList(link, FAMILY_V6)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nroutes := len(routes)
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.ParseIP("2001:db8::0"),
|
|
Mask: net.CIDRMask(64, 128),
|
|
}
|
|
route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Expires: 10}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V6)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != nroutes+1 {
|
|
t.Fatal("Route not added properly")
|
|
}
|
|
|
|
// route expiry is supported by kernel 4.4+
|
|
k, m, err := KernelVersion()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if k > 4 || (k == 4 && m > 4) {
|
|
foundExpires := false
|
|
for _, route := range routes {
|
|
if route.Dst.IP.Equal(dst.IP) {
|
|
if route.Expires > 0 && route.Expires <= 10 {
|
|
foundExpires = true
|
|
}
|
|
}
|
|
}
|
|
if !foundExpires {
|
|
t.Fatal("Route 'expires' not set")
|
|
}
|
|
}
|
|
|
|
dstIP := net.ParseIP("2001:db8::1")
|
|
routeToDstIP, err := RouteGet(dstIP)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// cleanup route
|
|
if len(routeToDstIP) == 0 {
|
|
t.Fatal("Route not present")
|
|
}
|
|
if err := RouteDel(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V6)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != nroutes {
|
|
t.Fatal("Route not removed properly")
|
|
}
|
|
|
|
// add a default link route
|
|
_, defaultDst, _ := net.ParseCIDR("::/0")
|
|
route = Route{LinkIndex: link.Attrs().Index, Dst: defaultDst}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V6)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != nroutes+1 {
|
|
t.Fatal("Default route not added properly")
|
|
}
|
|
|
|
// add a default link route
|
|
for _, route := range routes {
|
|
if route.Dst.String() == defaultDst.String() {
|
|
if err := RouteDel(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V6)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != nroutes {
|
|
t.Fatal("Default route not removed properly")
|
|
}
|
|
|
|
// add blackhole default link route
|
|
routes, err = RouteList(nil, FAMILY_V6)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nroutes = len(routes)
|
|
|
|
route = Route{Type: unix.RTN_BLACKHOLE, Dst: defaultDst}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(nil, FAMILY_V6)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != nroutes+1 {
|
|
t.Fatal("Blackhole default route not added properly")
|
|
}
|
|
|
|
// add blackhole default link route
|
|
for _, route := range routes {
|
|
if ipNetEqual(route.Dst, defaultDst) {
|
|
if err := RouteDel(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
routes, err = RouteList(nil, FAMILY_V6)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != nroutes {
|
|
t.Fatal("Blackhole default route not removed properly")
|
|
}
|
|
|
|
// add prohibit default link route
|
|
routes, err = RouteList(nil, FAMILY_V6)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nroutes = len(routes)
|
|
|
|
route = Route{Type: unix.RTN_BLACKHOLE, Dst: defaultDst}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(nil, FAMILY_V6)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != nroutes+1 {
|
|
t.Fatal("Prohibit default route not added properly")
|
|
}
|
|
|
|
// add prohibit default link route
|
|
for _, route := range routes {
|
|
if ipNetEqual(route.Dst, defaultDst) {
|
|
if err := RouteDel(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
routes, err = RouteList(nil, FAMILY_V6)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != nroutes {
|
|
t.Fatal("Prohibit default route not removed properly")
|
|
}
|
|
|
|
// cleanup dummy interface created for the test
|
|
if err := LinkDel(link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestRouteChange(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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)
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
|
|
ip := net.IPv4(127, 1, 1, 1)
|
|
route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
|
|
|
|
if err := RouteChange(&route); err == nil {
|
|
t.Fatal("Route added while it should fail")
|
|
}
|
|
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err := RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("Route not added properly")
|
|
}
|
|
|
|
ip = net.IPv4(127, 1, 1, 2)
|
|
route = Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
|
|
if err := RouteChange(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(routes) != 1 || !routes[0].Src.Equal(ip) {
|
|
t.Fatal("Route not changed properly")
|
|
}
|
|
|
|
if err := RouteDel(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 0 {
|
|
t.Fatal("Route not removed properly")
|
|
}
|
|
}
|
|
|
|
func TestRouteReplace(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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)
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
|
|
ip := net.IPv4(127, 1, 1, 1)
|
|
route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err := RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("Route not added properly")
|
|
}
|
|
|
|
ip = net.IPv4(127, 1, 1, 2)
|
|
route = Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
|
|
if err := RouteReplace(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(routes) != 1 || !routes[0].Src.Equal(ip) {
|
|
t.Fatal("Route not replaced properly")
|
|
}
|
|
|
|
if err := RouteDel(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 0 {
|
|
t.Fatal("Route not removed properly")
|
|
}
|
|
}
|
|
|
|
func TestRouteAppend(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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)
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
|
|
ip := net.IPv4(127, 1, 1, 1)
|
|
route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err := RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("Route not added properly")
|
|
}
|
|
|
|
ip = net.IPv4(127, 1, 1, 2)
|
|
route = Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
|
|
if err := RouteAppend(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(routes) != 2 || !routes[1].Src.Equal(ip) {
|
|
t.Fatal("Route not append properly")
|
|
}
|
|
|
|
if err := RouteDel(&routes[0]); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := RouteDel(&routes[1]); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 0 {
|
|
t.Fatal("Route not removed properly")
|
|
}
|
|
}
|
|
|
|
func TestRouteAddIncomplete(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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)
|
|
}
|
|
|
|
route := Route{LinkIndex: link.Attrs().Index}
|
|
if err := RouteAdd(&route); err == nil {
|
|
t.Fatal("Adding incomplete route should fail")
|
|
}
|
|
}
|
|
|
|
// expectRouteUpdate returns whether the expected updated is received within one minute.
|
|
func expectRouteUpdate(ch <-chan RouteUpdate, t, f uint16, dst net.IP) bool {
|
|
for {
|
|
timeout := time.After(time.Minute)
|
|
select {
|
|
case update := <-ch:
|
|
if update.Type == t &&
|
|
update.NlFlags == f &&
|
|
update.Route.Dst != nil &&
|
|
update.Route.Dst.IP.Equal(dst) {
|
|
return true
|
|
}
|
|
case <-timeout:
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRouteSubscribe(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
ch := make(chan RouteUpdate)
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
if err := RouteSubscribe(ch, done); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
|
|
ip := net.IPv4(127, 1, 1, 1)
|
|
route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !expectRouteUpdate(ch, unix.RTM_NEWROUTE, unix.NLM_F_EXCL|unix.NLM_F_CREATE, dst.IP) {
|
|
t.Fatal("Add update not received as expected")
|
|
}
|
|
if err := RouteDel(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !expectRouteUpdate(ch, unix.RTM_DELROUTE, 0, dst.IP) {
|
|
t.Fatal("Del update not received as expected")
|
|
}
|
|
}
|
|
|
|
func TestRouteSubscribeWithOptions(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
ch := make(chan RouteUpdate)
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
var lastError error
|
|
defer func() {
|
|
if lastError != nil {
|
|
t.Fatalf("Fatal error received during subscription: %v", lastError)
|
|
}
|
|
}()
|
|
if err := RouteSubscribeWithOptions(ch, done, RouteSubscribeOptions{
|
|
ErrorCallback: func(err error) {
|
|
lastError = err
|
|
},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
|
|
ip := net.IPv4(127, 1, 1, 1)
|
|
route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !expectRouteUpdate(ch, unix.RTM_NEWROUTE, unix.NLM_F_EXCL|unix.NLM_F_CREATE, dst.IP) {
|
|
t.Fatal("Add update not received as expected")
|
|
}
|
|
}
|
|
|
|
func TestRouteSubscribeAt(t *testing.T) {
|
|
skipUnlessRoot(t)
|
|
|
|
// Create an handle on a custom netns
|
|
newNs, err := netns.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer newNs.Close()
|
|
|
|
nh, err := NewHandleAt(newNs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer nh.Close()
|
|
|
|
// Subscribe for Route events on the custom netns
|
|
ch := make(chan RouteUpdate)
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
if err := RouteSubscribeAt(newNs, ch, done); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// get loopback interface
|
|
link, err := nh.LinkByName("lo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// bring the interface up
|
|
if err = nh.LinkSetUp(link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(192, 169, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
|
|
ip := net.IPv4(127, 100, 1, 1)
|
|
route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
|
|
if err := nh.RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !expectRouteUpdate(ch, unix.RTM_NEWROUTE, unix.NLM_F_EXCL|unix.NLM_F_CREATE, dst.IP) {
|
|
t.Fatal("Add update not received as expected")
|
|
}
|
|
if err := nh.RouteDel(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !expectRouteUpdate(ch, unix.RTM_DELROUTE, 0, dst.IP) {
|
|
t.Fatal("Del update not received as expected")
|
|
}
|
|
}
|
|
|
|
func TestRouteSubscribeListExisting(t *testing.T) {
|
|
skipUnlessRoot(t)
|
|
|
|
// Create an handle on a custom netns
|
|
newNs, err := netns.New()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer newNs.Close()
|
|
|
|
nh, err := NewHandleAt(newNs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer nh.Close()
|
|
|
|
// get loopback interface
|
|
link, err := nh.LinkByName("lo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// bring the interface up
|
|
if err = nh.LinkSetUp(link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// add a gateway route before subscribing
|
|
dst10 := &net.IPNet{
|
|
IP: net.IPv4(10, 10, 10, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
|
|
ip := net.IPv4(127, 100, 1, 1)
|
|
route10 := Route{LinkIndex: link.Attrs().Index, Dst: dst10, Src: ip}
|
|
if err := nh.RouteAdd(&route10); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Subscribe for Route events including existing routes
|
|
ch := make(chan RouteUpdate)
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
if err := RouteSubscribeWithOptions(ch, done, RouteSubscribeOptions{
|
|
Namespace: &newNs,
|
|
ListExisting: true},
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !expectRouteUpdate(ch, unix.RTM_NEWROUTE, 0, dst10.IP) {
|
|
t.Fatal("Existing add update not received as expected")
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(192, 169, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
|
|
route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Src: ip}
|
|
if err := nh.RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !expectRouteUpdate(ch, unix.RTM_NEWROUTE, unix.NLM_F_EXCL|unix.NLM_F_CREATE, dst.IP) {
|
|
t.Fatal("Add update not received as expected")
|
|
}
|
|
if err := nh.RouteDel(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !expectRouteUpdate(ch, unix.RTM_DELROUTE, 0, dst.IP) {
|
|
t.Fatal("Del update not received as expected")
|
|
}
|
|
if err := nh.RouteDel(&route10); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !expectRouteUpdate(ch, unix.RTM_DELROUTE, 0, dst10.IP) {
|
|
t.Fatal("Del update not received as expected")
|
|
}
|
|
}
|
|
|
|
func TestRouteFilterAllTables(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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)
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(1, 1, 1, 1),
|
|
Mask: net.CIDRMask(32, 32),
|
|
}
|
|
|
|
tables := []int{1000, 1001, 1002}
|
|
src := net.IPv4(127, 3, 3, 3)
|
|
for _, table := range tables {
|
|
route := Route{
|
|
LinkIndex: link.Attrs().Index,
|
|
Dst: dst,
|
|
Src: src,
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
Priority: 13,
|
|
Table: table,
|
|
Type: unix.RTN_UNICAST,
|
|
Tos: 12,
|
|
Hoplimit: 100,
|
|
Realm: 328,
|
|
}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
routes, err := RouteListFiltered(FAMILY_V4, &Route{
|
|
Dst: dst,
|
|
Src: src,
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
Table: unix.RT_TABLE_UNSPEC,
|
|
Type: unix.RTN_UNICAST,
|
|
Tos: 12,
|
|
Hoplimit: 100,
|
|
Realm: 328,
|
|
}, RT_FILTER_DST|RT_FILTER_SRC|RT_FILTER_SCOPE|RT_FILTER_TABLE|RT_FILTER_TYPE|RT_FILTER_TOS|RT_FILTER_HOPLIMIT|RT_FILTER_REALM)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 3 {
|
|
t.Fatal("Routes not added properly")
|
|
}
|
|
|
|
for _, route := range routes {
|
|
if route.Scope != unix.RT_SCOPE_LINK {
|
|
t.Fatal("Invalid Scope. Route not added properly")
|
|
}
|
|
if route.Priority != 13 {
|
|
t.Fatal("Invalid Priority. Route not added properly")
|
|
}
|
|
if !tableIDIn(tables, route.Table) {
|
|
t.Fatalf("Invalid Table %d. Route not added properly", route.Table)
|
|
}
|
|
if route.Type != unix.RTN_UNICAST {
|
|
t.Fatal("Invalid Type. Route not added properly")
|
|
}
|
|
if route.Tos != 12 {
|
|
t.Fatal("Invalid Tos. Route not added properly")
|
|
}
|
|
if route.Hoplimit != 100 {
|
|
t.Fatal("Invalid Hoplimit. Route not added properly")
|
|
}
|
|
if route.Realm != 328 {
|
|
t.Fatal("Invalid Realm. Route not added properly")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRouteFilterByFamily(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
const table int = 999
|
|
|
|
// 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)
|
|
}
|
|
|
|
// add a IPv4 gateway route
|
|
dst4 := &net.IPNet{
|
|
IP: net.IPv4(2, 2, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
route4 := Route{LinkIndex: link.Attrs().Index, Dst: dst4, Table: table}
|
|
if err := RouteAdd(&route4); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// add a IPv6 gateway route
|
|
dst6 := &net.IPNet{
|
|
IP: net.ParseIP("2001:db9::0"),
|
|
Mask: net.CIDRMask(64, 128),
|
|
}
|
|
route6 := Route{LinkIndex: link.Attrs().Index, Dst: dst6, Table: table}
|
|
if err := RouteAdd(&route6); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Get routes for both families
|
|
routes_all, err := RouteListFiltered(FAMILY_ALL, &Route{Table: table}, RT_FILTER_TABLE)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes_all) != 2 {
|
|
t.Fatal("Filtering by FAMILY_ALL doesn't find two routes")
|
|
}
|
|
|
|
// Get IPv4 route
|
|
routes_v4, err := RouteListFiltered(FAMILY_V4, &Route{Table: table}, RT_FILTER_TABLE)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes_v4) != 1 {
|
|
t.Fatal("Filtering by FAMILY_V4 doesn't find one route")
|
|
}
|
|
|
|
// Get IPv6 route
|
|
routes_v6, err := RouteListFiltered(FAMILY_V6, &Route{Table: table}, RT_FILTER_TABLE)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes_v6) != 1 {
|
|
t.Fatal("Filtering by FAMILY_V6 doesn't find one route")
|
|
}
|
|
|
|
// Get non-existent routes
|
|
routes_non_existent, err := RouteListFiltered(99, &Route{Table: table}, RT_FILTER_TABLE)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes_non_existent) != 0 {
|
|
t.Fatal("Filtering by non-existent family find some route")
|
|
}
|
|
}
|
|
|
|
func TestRouteFilterIterCanStop(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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)
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(1, 1, 1, 1),
|
|
Mask: net.CIDRMask(32, 32),
|
|
}
|
|
|
|
for i := 0; i < 3; i++ {
|
|
route := Route{
|
|
LinkIndex: link.Attrs().Index,
|
|
Dst: dst,
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
Priority: 1 + i,
|
|
Table: 1000,
|
|
Type: unix.RTN_UNICAST,
|
|
}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
var routes []Route
|
|
err = RouteListFilteredIter(FAMILY_V4, &Route{
|
|
Dst: dst,
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
Table: 1000,
|
|
Type: unix.RTN_UNICAST,
|
|
}, RT_FILTER_TABLE, func(route Route) (cont bool) {
|
|
routes = append(routes, route)
|
|
return len(routes) < 2
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 2 {
|
|
t.Fatal("Unexpected number of iterations")
|
|
}
|
|
for _, route := range routes {
|
|
if route.Scope != unix.RT_SCOPE_LINK {
|
|
t.Fatal("Invalid Scope. Route not added properly")
|
|
}
|
|
if route.Priority < 1 || route.Priority > 3 {
|
|
t.Fatal("Priority outside expected range. Route not added properly")
|
|
}
|
|
if route.Table != 1000 {
|
|
t.Fatalf("Invalid Table %d. Route not added properly", route.Table)
|
|
}
|
|
if route.Type != unix.RTN_UNICAST {
|
|
t.Fatal("Invalid Type. Route not added properly")
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkRouteListFilteredNew(b *testing.B) {
|
|
b.Cleanup(setUpNetlinkTest(b))
|
|
|
|
link, err := setUpRoutesBench(b)
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
var routes []Route
|
|
for i := 0; i < b.N; i++ {
|
|
routes, err = pkgHandle.RouteListFiltered(FAMILY_V4, &Route{
|
|
LinkIndex: link.Attrs().Index,
|
|
}, RT_FILTER_OIF)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
if len(routes) != 65535 {
|
|
b.Fatal("Incorrect number of routes.", len(routes))
|
|
}
|
|
}
|
|
runtime.KeepAlive(routes)
|
|
}
|
|
|
|
func BenchmarkRouteListIter(b *testing.B) {
|
|
b.Cleanup(setUpNetlinkTest(b))
|
|
|
|
link, err := setUpRoutesBench(b)
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
var routes int
|
|
err = RouteListFilteredIter(FAMILY_V4, &Route{
|
|
LinkIndex: link.Attrs().Index,
|
|
}, RT_FILTER_OIF, func(route Route) (cont bool) {
|
|
routes++
|
|
return true
|
|
})
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
if routes != 65535 {
|
|
b.Fatal("Incorrect number of routes.", routes)
|
|
}
|
|
}
|
|
}
|
|
|
|
func setUpRoutesBench(b *testing.B) (Link, error) {
|
|
// get loopback interface
|
|
link, err := LinkByName("lo")
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
// bring the interface up
|
|
if err = LinkSetUp(link); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
// add a gateway route
|
|
for i := 0; i < 65535; i++ {
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(1, 1, byte(i>>8), byte(i&0xff)),
|
|
Mask: net.CIDRMask(32, 32),
|
|
}
|
|
route := Route{
|
|
LinkIndex: link.Attrs().Index,
|
|
Dst: dst,
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
Priority: 10,
|
|
Type: unix.RTN_UNICAST,
|
|
}
|
|
if err := RouteAdd(&route); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
return link, err
|
|
}
|
|
|
|
func tableIDIn(ids []int, id int) bool {
|
|
for _, v := range ids {
|
|
if v == id {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func TestRouteExtraFields(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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)
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(1, 1, 1, 1),
|
|
Mask: net.CIDRMask(32, 32),
|
|
}
|
|
|
|
src := net.IPv4(127, 3, 3, 3)
|
|
route := Route{
|
|
LinkIndex: link.Attrs().Index,
|
|
Dst: dst,
|
|
Src: src,
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
Priority: 13,
|
|
Table: unix.RT_TABLE_MAIN,
|
|
Type: unix.RTN_UNICAST,
|
|
Tos: 12,
|
|
Hoplimit: 100,
|
|
Realm: 239,
|
|
}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err := RouteListFiltered(FAMILY_V4, &Route{
|
|
Dst: dst,
|
|
Src: src,
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
Table: unix.RT_TABLE_MAIN,
|
|
Type: unix.RTN_UNICAST,
|
|
Tos: 12,
|
|
Hoplimit: 100,
|
|
Realm: 239,
|
|
}, RT_FILTER_DST|RT_FILTER_SRC|RT_FILTER_SCOPE|RT_FILTER_TABLE|RT_FILTER_TYPE|RT_FILTER_TOS|RT_FILTER_HOPLIMIT|RT_FILTER_REALM)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("Route not added properly")
|
|
}
|
|
|
|
if routes[0].Scope != unix.RT_SCOPE_LINK {
|
|
t.Fatal("Invalid Scope. Route not added properly")
|
|
}
|
|
if routes[0].Priority != 13 {
|
|
t.Fatal("Invalid Priority. Route not added properly")
|
|
}
|
|
if routes[0].Table != unix.RT_TABLE_MAIN {
|
|
t.Fatal("Invalid Scope. Route not added properly")
|
|
}
|
|
if routes[0].Type != unix.RTN_UNICAST {
|
|
t.Fatal("Invalid Type. Route not added properly")
|
|
}
|
|
if routes[0].Tos != 12 {
|
|
t.Fatal("Invalid Tos. Route not added properly")
|
|
}
|
|
if routes[0].Hoplimit != 100 {
|
|
t.Fatal("Invalid Hoplimit. Route not added properly")
|
|
}
|
|
if routes[0].Realm != 239 {
|
|
t.Fatal("Invalid Realm. Route not added properly")
|
|
}
|
|
}
|
|
|
|
func TestRouteMultiPath(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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)
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
|
|
idx := link.Attrs().Index
|
|
route := Route{Dst: dst, MultiPath: []*NexthopInfo{{LinkIndex: idx}, {LinkIndex: idx}}}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err := RouteList(nil, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("MultiPath Route not added properly")
|
|
}
|
|
if len(routes[0].MultiPath) != 2 {
|
|
t.Fatal("MultiPath Route not added properly")
|
|
}
|
|
}
|
|
|
|
func TestRouteIifOption(t *testing.T) {
|
|
skipUnlessRoot(t)
|
|
|
|
runtime.LockOSThread()
|
|
t.Cleanup(runtime.UnlockOSThread)
|
|
|
|
rootNs, err := netns.GetFromPid(1)
|
|
if err != nil {
|
|
t.Fatalf("could not get root ns: %s", err)
|
|
}
|
|
t.Cleanup(func() { rootNs.Close() })
|
|
|
|
rootHdl, err := NewHandleAt(rootNs)
|
|
if err != nil {
|
|
t.Fatalf("could not create handle for root ns: %s", err)
|
|
}
|
|
t.Cleanup(func() { rootHdl.Close() })
|
|
|
|
// setup a veth pair across two namespaces
|
|
// veth1 (2.2.2.3/24) <-> veth2 (2.2.2.4/24)
|
|
|
|
// peer ns for veth pair
|
|
ns, err := netns.New()
|
|
if err != nil {
|
|
t.Fatalf("could not create new ns: %s", err)
|
|
}
|
|
t.Cleanup(func() { ns.Close() })
|
|
|
|
l := &Veth{
|
|
LinkAttrs: LinkAttrs{Name: "veth1"},
|
|
PeerName: "veth2",
|
|
PeerNamespace: NsFd(ns),
|
|
}
|
|
if err = rootHdl.LinkAdd(l); err != nil {
|
|
t.Fatalf("could not add veth interface: %s", err)
|
|
}
|
|
t.Cleanup(func() { rootHdl.LinkDel(l) })
|
|
|
|
ve1, err := rootHdl.LinkByName("veth1")
|
|
if err != nil {
|
|
t.Fatalf("could not get link veth1: %s", err)
|
|
}
|
|
|
|
err = rootHdl.AddrAdd(ve1, &Addr{IPNet: &net.IPNet{IP: net.ParseIP("2.2.2.3"), Mask: net.CIDRMask(24, 32)}})
|
|
if err != nil {
|
|
t.Fatalf("could not set address for veth1: %s", err)
|
|
}
|
|
|
|
nh, err := NewHandleAt(ns)
|
|
if err != nil {
|
|
t.Fatalf("could not get handle for ns %+v: %s", ns, err)
|
|
}
|
|
t.Cleanup(func() { nh.Close() })
|
|
|
|
ve2, err := nh.LinkByName("veth2")
|
|
if err != nil {
|
|
t.Fatalf("could not get link veth2: %s", err)
|
|
}
|
|
|
|
err = nh.AddrAdd(ve2, &Addr{IPNet: &net.IPNet{IP: net.ParseIP("2.2.2.4"), Mask: net.CIDRMask(24, 32)}})
|
|
if err != nil {
|
|
t.Fatalf("could set address for veth2: %s", err)
|
|
}
|
|
|
|
if err = rootHdl.LinkSetUp(ve1); err != nil {
|
|
t.Fatalf("could not set veth1 up: %s", err)
|
|
}
|
|
|
|
if err = nh.LinkSetUp(ve2); err != nil {
|
|
t.Fatalf("could not set veth2 up: %s", err)
|
|
}
|
|
|
|
err = nh.RouteAdd(&Route{
|
|
Dst: &net.IPNet{
|
|
IP: net.IPv4zero,
|
|
Mask: net.CIDRMask(0, 32),
|
|
},
|
|
Gw: net.ParseIP("2.2.2.3"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("could not add default route to ns: %s", err)
|
|
}
|
|
|
|
// setup finished, now do the actual test
|
|
|
|
_, err = rootHdl.RouteGetWithOptions(net.ParseIP("8.8.8.8"), &RouteGetOptions{
|
|
SrcAddr: net.ParseIP("2.2.2.4"),
|
|
})
|
|
if err == nil {
|
|
t.Fatal("route get should have resulted in error but did not")
|
|
}
|
|
|
|
testWithOptions := func(opts *RouteGetOptions) {
|
|
routes, err := rootHdl.RouteGetWithOptions(net.ParseIP("8.8.8.8"), opts)
|
|
if err != nil {
|
|
t.Fatalf("could not get route: %s", err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatalf("did not get exactly one route, routes: %+v", routes)
|
|
}
|
|
|
|
// should be the default route
|
|
r, err := rootHdl.RouteGet(net.ParseIP("8.8.8.8"))
|
|
if err != nil {
|
|
t.Fatalf("could not get default route for 8.8.8.8: %s", err)
|
|
}
|
|
if len(r) != 1 {
|
|
t.Fatalf("did not get exactly one route, routes: %+v", routes)
|
|
}
|
|
if !routes[0].Gw.Equal(r[0].Gw) {
|
|
t.Fatalf("wrong gateway in route: expected: %s, got: %s", r[0].Gw, routes[0].Gw)
|
|
}
|
|
if routes[0].LinkIndex != r[0].LinkIndex {
|
|
t.Fatalf("wrong link in route: expected: %d, got: %d", r[0].LinkIndex, routes[0].LinkIndex)
|
|
}
|
|
}
|
|
|
|
t.Run("with iif", func(t *testing.T) {
|
|
testWithOptions(&RouteGetOptions{
|
|
SrcAddr: net.ParseIP("2.2.2.4"),
|
|
Iif: "veth1",
|
|
})
|
|
})
|
|
|
|
t.Run("with iifIndex", func(t *testing.T) {
|
|
testWithOptions(&RouteGetOptions{
|
|
SrcAddr: net.ParseIP("2.2.2.4"),
|
|
IifIndex: ve1.Attrs().Index,
|
|
})
|
|
})
|
|
|
|
t.Run("with iif and iifIndex", func(t *testing.T) {
|
|
testWithOptions(&RouteGetOptions{
|
|
SrcAddr: net.ParseIP("2.2.2.4"),
|
|
Iif: "veth1",
|
|
IifIndex: ve2.Attrs().Index, // Iif will supersede here
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestRouteOifOption(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// setup two interfaces: eth0, eth1
|
|
err := LinkAdd(&Dummy{LinkAttrs{Name: "eth0"}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
link1, err := LinkByName("eth0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err = LinkSetUp(link1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err = LinkAdd(&Dummy{LinkAttrs{Name: "eth1"}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
link2, err := LinkByName("eth1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err = LinkSetUp(link2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// config ip addresses on interfaces
|
|
addr1 := &Addr{
|
|
IPNet: &net.IPNet{
|
|
IP: net.IPv4(192, 168, 1, 1),
|
|
Mask: net.CIDRMask(24, 32),
|
|
},
|
|
}
|
|
|
|
if err = AddrAdd(link1, addr1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
addr2 := &Addr{
|
|
IPNet: &net.IPNet{
|
|
IP: net.IPv4(192, 168, 2, 1),
|
|
Mask: net.CIDRMask(24, 32),
|
|
},
|
|
}
|
|
|
|
if err = AddrAdd(link2, addr2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// add default multipath route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(0, 0, 0, 0),
|
|
Mask: net.CIDRMask(0, 32),
|
|
}
|
|
gw1 := net.IPv4(192, 168, 1, 254)
|
|
gw2 := net.IPv4(192, 168, 2, 254)
|
|
route := Route{Dst: dst, MultiPath: []*NexthopInfo{{LinkIndex: link1.Attrs().Index,
|
|
Gw: gw1}, {LinkIndex: link2.Attrs().Index, Gw: gw2}}}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// check getting route from specified Oif
|
|
dstIP := net.IPv4(10, 1, 1, 1)
|
|
routes, err := RouteGetWithOptions(dstIP, &RouteGetOptions{Oif: "eth0"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(routes) != 1 || routes[0].LinkIndex != link1.Attrs().Index ||
|
|
!routes[0].Gw.Equal(gw1) {
|
|
t.Fatal("Get route from unmatched interface")
|
|
}
|
|
|
|
// check getting route from specified Oifindex
|
|
routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{OifIndex: link1.Attrs().Index})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 || routes[0].LinkIndex != link1.Attrs().Index ||
|
|
!routes[0].Gw.Equal(gw1) {
|
|
t.Fatal("Get route from unmatched interface")
|
|
}
|
|
|
|
routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{Oif: "eth1"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(routes) != 1 || routes[0].LinkIndex != link2.Attrs().Index ||
|
|
!routes[0].Gw.Equal(gw2) {
|
|
t.Fatal("Get route from unmatched interface")
|
|
}
|
|
|
|
routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{OifIndex: link2.Attrs().Index})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 || routes[0].LinkIndex != link2.Attrs().Index ||
|
|
!routes[0].Gw.Equal(gw2) {
|
|
t.Fatal("Get route from unmatched interface")
|
|
}
|
|
}
|
|
|
|
func TestFilterDefaultRoute(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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)
|
|
}
|
|
|
|
address := &Addr{
|
|
IPNet: &net.IPNet{
|
|
IP: net.IPv4(127, 0, 0, 2),
|
|
Mask: net.CIDRMask(24, 32),
|
|
},
|
|
}
|
|
if err = AddrAdd(link, address); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Add default route
|
|
gw := net.IPv4(127, 0, 0, 2)
|
|
|
|
defaultRoute := Route{
|
|
Dst: nil,
|
|
Gw: gw,
|
|
}
|
|
|
|
if err := RouteAdd(&defaultRoute); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// add an extra route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
|
|
extraRoute := Route{
|
|
Dst: dst,
|
|
Gw: gw,
|
|
}
|
|
|
|
if err := RouteAdd(&extraRoute); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var filterTests = []struct {
|
|
filter *Route
|
|
mask uint64
|
|
expected net.IP
|
|
}{
|
|
{
|
|
&Route{Dst: nil},
|
|
RT_FILTER_DST,
|
|
gw,
|
|
},
|
|
{
|
|
&Route{Dst: dst},
|
|
RT_FILTER_DST,
|
|
gw,
|
|
},
|
|
}
|
|
|
|
for _, f := range filterTests {
|
|
routes, err := RouteListFiltered(FAMILY_V4, f.filter, f.mask)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("Route not filtered properly")
|
|
}
|
|
if !routes[0].Gw.Equal(gw) {
|
|
t.Fatal("Unexpected Gateway")
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func TestMPLSRouteAddDel(t *testing.T) {
|
|
t.Cleanup(setUpMPLSNetlinkTest(t))
|
|
|
|
// 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")
|
|
}
|
|
|
|
}
|
|
|
|
func TestIP6tnlRouteAddDel(t *testing.T) {
|
|
_, err := RouteList(nil, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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)
|
|
}
|
|
|
|
_, dst, err := net.ParseCIDR("192.168.99.0/24")
|
|
if err != nil {
|
|
t.Fatalf("cannot parse destination prefix: %v", err)
|
|
}
|
|
|
|
encap := IP6tnlEncap{
|
|
Dst: net.ParseIP("2001:db8::"),
|
|
Src: net.ParseIP("::"),
|
|
}
|
|
|
|
route := &Route{
|
|
LinkIndex: link.Attrs().Index,
|
|
Dst: dst,
|
|
Encap: &encap,
|
|
}
|
|
|
|
if err := RouteAdd(route); err != nil {
|
|
t.Fatalf("Cannot add route: %v", err)
|
|
}
|
|
routes, err := RouteList(link, FAMILY_V4)
|
|
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_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 0 {
|
|
t.Fatal("Route not removed properly")
|
|
}
|
|
|
|
}
|
|
|
|
func TestRouteEqual(t *testing.T) {
|
|
mplsDst := 100
|
|
seg6encap := &SEG6Encap{Mode: nl.SEG6_IPTUN_MODE_ENCAP}
|
|
seg6encap.Segments = []net.IP{net.ParseIP("fc00:a000::11")}
|
|
cases := []Route{
|
|
{
|
|
Dst: nil,
|
|
Gw: net.IPv4(1, 1, 1, 1),
|
|
},
|
|
{
|
|
LinkIndex: 20,
|
|
Dst: nil,
|
|
Gw: net.IPv4(1, 1, 1, 1),
|
|
},
|
|
{
|
|
ILinkIndex: 21,
|
|
LinkIndex: 20,
|
|
Dst: nil,
|
|
Gw: net.IPv4(1, 1, 1, 1),
|
|
},
|
|
{
|
|
LinkIndex: 20,
|
|
Dst: nil,
|
|
Protocol: 20,
|
|
Gw: net.IPv4(1, 1, 1, 1),
|
|
},
|
|
{
|
|
LinkIndex: 20,
|
|
Dst: nil,
|
|
Priority: 20,
|
|
Gw: net.IPv4(1, 1, 1, 1),
|
|
},
|
|
{
|
|
LinkIndex: 20,
|
|
Dst: nil,
|
|
Type: 20,
|
|
Gw: net.IPv4(1, 1, 1, 1),
|
|
},
|
|
{
|
|
LinkIndex: 20,
|
|
Dst: nil,
|
|
Table: 200,
|
|
Gw: net.IPv4(1, 1, 1, 1),
|
|
},
|
|
{
|
|
LinkIndex: 20,
|
|
Dst: nil,
|
|
Tos: 1,
|
|
Gw: net.IPv4(1, 1, 1, 1),
|
|
},
|
|
{
|
|
LinkIndex: 20,
|
|
Dst: nil,
|
|
Hoplimit: 1,
|
|
Gw: net.IPv4(1, 1, 1, 1),
|
|
},
|
|
{
|
|
LinkIndex: 20,
|
|
Dst: nil,
|
|
Realm: 29,
|
|
Gw: net.IPv4(1, 1, 1, 1),
|
|
},
|
|
{
|
|
LinkIndex: 20,
|
|
Dst: nil,
|
|
Flags: int(FLAG_ONLINK),
|
|
Gw: net.IPv4(1, 1, 1, 1),
|
|
},
|
|
{
|
|
LinkIndex: 10,
|
|
Dst: &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
},
|
|
Src: net.IPv4(127, 1, 1, 1),
|
|
},
|
|
{
|
|
LinkIndex: 10,
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
Dst: &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
},
|
|
Src: net.IPv4(127, 1, 1, 1),
|
|
},
|
|
{
|
|
LinkIndex: 3,
|
|
Dst: &net.IPNet{
|
|
IP: net.IPv4(1, 1, 1, 1),
|
|
Mask: net.CIDRMask(32, 32),
|
|
},
|
|
Src: net.IPv4(127, 3, 3, 3),
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
Priority: 13,
|
|
Table: unix.RT_TABLE_MAIN,
|
|
Type: unix.RTN_UNICAST,
|
|
Tos: 12,
|
|
},
|
|
{
|
|
LinkIndex: 3,
|
|
Dst: &net.IPNet{
|
|
IP: net.IPv4(1, 1, 1, 1),
|
|
Mask: net.CIDRMask(32, 32),
|
|
},
|
|
Src: net.IPv4(127, 3, 3, 3),
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
Priority: 13,
|
|
Table: unix.RT_TABLE_MAIN,
|
|
Type: unix.RTN_UNICAST,
|
|
Hoplimit: 100,
|
|
},
|
|
{
|
|
LinkIndex: 3,
|
|
Dst: &net.IPNet{
|
|
IP: net.IPv4(1, 1, 1, 1),
|
|
Mask: net.CIDRMask(32, 32),
|
|
},
|
|
Src: net.IPv4(127, 3, 3, 3),
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
Priority: 13,
|
|
Table: unix.RT_TABLE_MAIN,
|
|
Type: unix.RTN_UNICAST,
|
|
Realm: 129,
|
|
},
|
|
{
|
|
LinkIndex: 10,
|
|
MPLSDst: &mplsDst,
|
|
NewDst: &MPLSDestination{
|
|
Labels: []int{200, 300},
|
|
},
|
|
},
|
|
{
|
|
Dst: nil,
|
|
Gw: net.IPv4(1, 1, 1, 1),
|
|
Encap: &MPLSEncap{
|
|
Labels: []int{100},
|
|
},
|
|
},
|
|
{
|
|
LinkIndex: 10,
|
|
Dst: &net.IPNet{
|
|
IP: net.IPv4(10, 0, 0, 102),
|
|
Mask: net.CIDRMask(32, 32),
|
|
},
|
|
Encap: seg6encap,
|
|
},
|
|
{
|
|
Dst: nil,
|
|
MultiPath: []*NexthopInfo{{LinkIndex: 10}, {LinkIndex: 20}},
|
|
},
|
|
{
|
|
Dst: nil,
|
|
MultiPath: []*NexthopInfo{{
|
|
LinkIndex: 10,
|
|
Gw: net.IPv4(1, 1, 1, 1),
|
|
}, {LinkIndex: 20}},
|
|
},
|
|
{
|
|
Dst: nil,
|
|
MultiPath: []*NexthopInfo{{
|
|
LinkIndex: 10,
|
|
Gw: net.IPv4(1, 1, 1, 1),
|
|
Encap: &MPLSEncap{
|
|
Labels: []int{100},
|
|
},
|
|
}, {LinkIndex: 20}},
|
|
},
|
|
{
|
|
Dst: nil,
|
|
MultiPath: []*NexthopInfo{{
|
|
LinkIndex: 10,
|
|
NewDst: &MPLSDestination{
|
|
Labels: []int{200, 300},
|
|
},
|
|
}, {LinkIndex: 20}},
|
|
},
|
|
{
|
|
Dst: nil,
|
|
MultiPath: []*NexthopInfo{{
|
|
LinkIndex: 10,
|
|
Encap: seg6encap,
|
|
}, {LinkIndex: 20}},
|
|
},
|
|
}
|
|
for i1 := range cases {
|
|
for i2 := range cases {
|
|
got := cases[i1].Equal(cases[i2])
|
|
expected := i1 == i2
|
|
if got != expected {
|
|
t.Errorf("Equal(%q,%q) == %s but expected %s",
|
|
cases[i1], cases[i2],
|
|
strconv.FormatBool(got),
|
|
strconv.FormatBool(expected))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIPNetEqual(t *testing.T) {
|
|
cases := []string{
|
|
"1.1.1.1/24", "1.1.1.0/24", "1.1.1.1/32",
|
|
"0.0.0.0/0", "0.0.0.0/14",
|
|
"2001:db8::/32", "2001:db8::/128",
|
|
"2001:db8::caff/32", "2001:db8::caff/128",
|
|
"",
|
|
}
|
|
for _, c1 := range cases {
|
|
var n1 *net.IPNet
|
|
if c1 != "" {
|
|
var i1 net.IP
|
|
var err1 error
|
|
i1, n1, err1 = net.ParseCIDR(c1)
|
|
if err1 != nil {
|
|
panic(err1)
|
|
}
|
|
n1.IP = i1
|
|
}
|
|
for _, c2 := range cases {
|
|
var n2 *net.IPNet
|
|
if c2 != "" {
|
|
var i2 net.IP
|
|
var err2 error
|
|
i2, n2, err2 = net.ParseCIDR(c2)
|
|
if err2 != nil {
|
|
panic(err2)
|
|
}
|
|
n2.IP = i2
|
|
}
|
|
|
|
got := ipNetEqual(n1, n2)
|
|
expected := c1 == c2
|
|
if got != expected {
|
|
t.Errorf("IPNetEqual(%q,%q) == %s but expected %s",
|
|
c1, c2,
|
|
strconv.FormatBool(got),
|
|
strconv.FormatBool(expected))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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_dt46 [nl.SEG6_LOCAL_MAX]bool
|
|
flags_end_dt46[nl.SEG6_LOCAL_ACTION] = true
|
|
flags_end_dt46[nl.SEG6_LOCAL_VRFTABLE] = 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
|
|
var flags_end_bpf [nl.SEG6_LOCAL_MAX]bool
|
|
flags_end_bpf[nl.SEG6_LOCAL_ACTION] = true
|
|
flags_end_bpf[nl.SEG6_LOCAL_BPF] = 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_dt46,
|
|
Action: nl.SEG6_LOCAL_ACTION_END_DT46,
|
|
VrfTable: 50,
|
|
},
|
|
{
|
|
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,
|
|
},
|
|
}
|
|
|
|
// SEG6_LOCAL_ACTION_END_BPF
|
|
endBpf := SEG6LocalEncap{
|
|
Flags: flags_end_bpf,
|
|
Action: nl.SEG6_LOCAL_ACTION_END_BPF,
|
|
}
|
|
_ = endBpf.SetProg(1, "firewall")
|
|
cases = append(cases, endBpf)
|
|
|
|
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 interface.
|
|
// Test both seg6 modes: encap (IPv4) & inline (IPv6).
|
|
t.Cleanup(setUpSEG6NetlinkTest(t))
|
|
|
|
// loopback doesn't work on recent kernels, so use a dummy
|
|
err := LinkAdd(&Dummy{LinkAttrs{Name: "dummy0"}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
link, err := LinkByName("dummy0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if err := LinkDel(link); err != nil {
|
|
t.Logf("failed to delete device: %v", err)
|
|
}
|
|
})
|
|
|
|
if err := LinkSetUp(link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
dst1 := &net.IPNet{ // INLINE mode must be IPv6 route
|
|
IP: net.ParseIP("2001:db8::1"),
|
|
Mask: net.CIDRMask(128, 128),
|
|
}
|
|
dst2 := &net.IPNet{
|
|
IP: net.IPv4(10, 0, 0, 102),
|
|
Mask: net.CIDRMask(32, 32),
|
|
}
|
|
var s1, s2 []net.IP
|
|
s1 = append(s1, net.ParseIP("fc00:a000::12"))
|
|
s1 = append(s1, net.ParseIP("fc00:a000::11"))
|
|
s2 = append(s2, net.ParseIP("fc00:a000::22"))
|
|
s2 = append(s2, net.ParseIP("fc00:a000::21"))
|
|
e1 := &SEG6Encap{Mode: nl.SEG6_IPTUN_MODE_INLINE}
|
|
e2 := &SEG6Encap{Mode: nl.SEG6_IPTUN_MODE_ENCAP}
|
|
e1.Segments = s1
|
|
e2.Segments = s2
|
|
route1 := Route{LinkIndex: link.Attrs().Index, Dst: dst1, Encap: e1}
|
|
route2 := Route{LinkIndex: link.Attrs().Index, Dst: dst2, Encap: e2}
|
|
|
|
// Add SEG6 routes
|
|
if err := RouteAdd(&route1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := RouteAdd(&route2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// SEG6_IPTUN_MODE_INLINE
|
|
// Kernel adds multiple routes so filter them
|
|
filtV6 := &Route{LinkIndex: link.Attrs().Index, Dst: dst1}
|
|
routes, err := RouteListFiltered(FAMILY_V6, filtV6, RT_FILTER_OIF|RT_FILTER_DST)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("SEG6 routes not added properly")
|
|
}
|
|
for _, route := range routes {
|
|
if route.Encap == nil || route.Encap.Type() != nl.LWTUNNEL_ENCAP_SEG6 {
|
|
t.Fatal("Invalid Type. SEG6_IPTUN_MODE_INLINE routes not added properly")
|
|
}
|
|
}
|
|
// SEG6_IPTUN_MODE_ENCAP
|
|
// Kernel adds multiple routes so filter them
|
|
filtV4 := &Route{LinkIndex: link.Attrs().Index, Dst: dst2}
|
|
routes, err = RouteListFiltered(FAMILY_V4, filtV4, RT_FILTER_OIF|RT_FILTER_DST)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("SEG6 routes not added properly")
|
|
}
|
|
for _, route := range routes {
|
|
if route.Encap.Type() != nl.LWTUNNEL_ENCAP_SEG6 {
|
|
t.Fatal("Invalid Type. SEG6_IPTUN_MODE_ENCAP routes not added properly")
|
|
}
|
|
}
|
|
|
|
// Del (remove) SEG6 routes
|
|
if err := RouteDel(&route1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := RouteDel(&route2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 0 {
|
|
t.Fatal("SEG6 routes not removed properly")
|
|
}
|
|
}
|
|
|
|
// add/del routes with LWTUNNEL_ENCAP_SEG6_LOCAL to/from dummy interface.
|
|
func TestSEG6LocalRoute6AddDel(t *testing.T) {
|
|
minKernelRequired(t, 4, 14)
|
|
t.Cleanup(setUpSEG6NetlinkTest(t))
|
|
|
|
// 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.
|
|
if _, err = RouteGet(dst1.IP); 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 TestBpfEncap(t *testing.T) {
|
|
tCase := &BpfEncap{}
|
|
if err := tCase.SetProg(nl.LWT_BPF_IN, 0, "test_in"); err == nil {
|
|
t.Fatal("BpfEncap: inserting invalid FD did not return error")
|
|
}
|
|
if err := tCase.SetProg(nl.LWT_BPF_XMIT_HEADROOM, 23, "test_nout"); err == nil {
|
|
t.Fatal("BpfEncap: inserting invalid mode did not return error")
|
|
}
|
|
if err := tCase.SetProg(nl.LWT_BPF_XMIT, 12, "test_xmit"); err != nil {
|
|
t.Fatal("BpfEncap: inserting valid program option returned error")
|
|
}
|
|
if err := tCase.SetXmitHeadroom(12); err != nil {
|
|
t.Fatal("BpfEncap: inserting valid headroom returned error")
|
|
}
|
|
if err := tCase.SetXmitHeadroom(nl.LWT_BPF_MAX_HEADROOM + 1); err == nil {
|
|
t.Fatal("BpfEncap: inserting invalid headroom did not return error")
|
|
}
|
|
tCase = &BpfEncap{}
|
|
|
|
expected := &BpfEncap{
|
|
progs: [nl.LWT_BPF_MAX]bpfObj{
|
|
1: {
|
|
progName: "test_in[fd:10]",
|
|
progFd: 10,
|
|
},
|
|
2: {
|
|
progName: "test_out[fd:11]",
|
|
progFd: 11,
|
|
},
|
|
3: {
|
|
progName: "test_xmit[fd:21]",
|
|
progFd: 21,
|
|
},
|
|
},
|
|
headroom: 128,
|
|
}
|
|
|
|
_ = tCase.SetProg(1, 10, "test_in")
|
|
_ = tCase.SetProg(2, 11, "test_out")
|
|
_ = tCase.SetProg(3, 21, "test_xmit")
|
|
_ = tCase.SetXmitHeadroom(128)
|
|
if !tCase.Equal(expected) {
|
|
t.Fatal("BpfEncap: equal comparison failed")
|
|
}
|
|
_ = tCase.SetProg(3, 21, "test2_xmit")
|
|
if tCase.Equal(expected) {
|
|
t.Fatal("BpfEncap: equal comparison succeeded when attributes differ")
|
|
}
|
|
}
|
|
|
|
func TestMTURouteAddDel(t *testing.T) {
|
|
_, err := RouteList(nil, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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)
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
|
|
route := Route{LinkIndex: link.Attrs().Index, Dst: dst, MTU: 500}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err := RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("Route not added properly")
|
|
}
|
|
|
|
if route.MTU != routes[0].MTU {
|
|
t.Fatal("Route mtu not set properly")
|
|
}
|
|
|
|
if err := RouteDel(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 0 {
|
|
t.Fatal("Route not removed properly")
|
|
}
|
|
}
|
|
|
|
func TestMTULockRouteAddDel(t *testing.T) {
|
|
_, err := RouteList(nil, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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)
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
|
|
route := Route{LinkIndex: link.Attrs().Index, Dst: dst, MTU: 500, MTULock: true}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err := RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("Route not added properly")
|
|
}
|
|
|
|
if route.MTU != routes[0].MTU {
|
|
t.Fatal("Route MTU not set properly")
|
|
}
|
|
|
|
if route.MTULock != routes[0].MTULock {
|
|
t.Fatal("Route MTU lock not set properly")
|
|
}
|
|
|
|
if err := RouteDel(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 0 {
|
|
t.Fatal("Route not removed properly")
|
|
}
|
|
}
|
|
|
|
func TestRtoMinLockRouteAddDel(t *testing.T) {
|
|
_, err := RouteList(nil, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// 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)
|
|
}
|
|
|
|
// add a gateway route
|
|
dst := &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
|
|
route := Route{LinkIndex: link.Attrs().Index, Dst: dst, RtoMin: 40, RtoMinLock: true}
|
|
if err := RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err := RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("Route not added properly")
|
|
}
|
|
|
|
if route.RtoMin != routes[0].RtoMin {
|
|
t.Fatal("Route RtoMin not set properly")
|
|
}
|
|
|
|
if route.RtoMinLock != routes[0].RtoMinLock {
|
|
t.Fatal("Route RtoMin lock not set properly")
|
|
}
|
|
|
|
if err := RouteDel(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 0 {
|
|
t.Fatal("Route not removed properly")
|
|
}
|
|
}
|
|
|
|
func TestRouteViaAddDel(t *testing.T) {
|
|
minKernelRequired(t, 5, 4)
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
_, err := RouteList(nil, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
link, err := LinkByName("lo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := LinkSetUp(link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
route := &Route{
|
|
LinkIndex: link.Attrs().Index,
|
|
Dst: &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
},
|
|
MultiPath: []*NexthopInfo{
|
|
{
|
|
LinkIndex: link.Attrs().Index,
|
|
Via: &Via{
|
|
AddrFamily: FAMILY_V6,
|
|
Addr: net.ParseIP("2001::1"),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
if err := RouteAdd(route); err != nil {
|
|
t.Fatalf("route: %v, err: %v", route, err)
|
|
}
|
|
|
|
routes, err := RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatal("Route not added properly")
|
|
}
|
|
|
|
got := routes[0].Via
|
|
want := route.MultiPath[0].Via
|
|
if !want.Equal(got) {
|
|
t.Fatalf("Route Via attribute does not match; got: %s, want: %s", got, want)
|
|
}
|
|
|
|
if err := RouteDel(route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 0 {
|
|
t.Fatal("Route not removed properly")
|
|
}
|
|
}
|
|
|
|
func TestRouteUIDOption(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// setup eth0 so that network is reachable
|
|
err := LinkAdd(&Dummy{LinkAttrs{Name: "eth0"}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
link, err := LinkByName("eth0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = LinkSetUp(link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
addr := &Addr{
|
|
IPNet: &net.IPNet{
|
|
IP: net.IPv4(192, 168, 1, 1),
|
|
Mask: net.CIDRMask(16, 32),
|
|
},
|
|
}
|
|
if err = AddrAdd(link, addr); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// a table different than unix.RT_TABLE_MAIN
|
|
testtable := 1000
|
|
|
|
gw1 := net.IPv4(192, 168, 1, 254)
|
|
gw2 := net.IPv4(192, 168, 2, 254)
|
|
|
|
// add default route via gw1 (in main route table by default)
|
|
defaultRouteMain := Route{
|
|
Dst: nil,
|
|
Gw: gw1,
|
|
}
|
|
if err := RouteAdd(&defaultRouteMain); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// add default route via gw2 in test route table
|
|
defaultRouteTest := Route{
|
|
Dst: nil,
|
|
Gw: gw2,
|
|
Table: testtable,
|
|
}
|
|
if err := RouteAdd(&defaultRouteTest); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// check the routes are in different tables
|
|
routes, err := RouteListFiltered(FAMILY_V4, &Route{
|
|
Dst: nil,
|
|
Table: unix.RT_TABLE_UNSPEC,
|
|
}, RT_FILTER_DST|RT_FILTER_TABLE)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 2 || routes[0].Table == routes[1].Table {
|
|
t.Fatal("Routes not added properly")
|
|
}
|
|
|
|
// add a rule that uidrange match should result in route lookup of test table for uid other than current
|
|
// current uid is 0 due to skipUnlessRoot()
|
|
var uid uint32 = 1000
|
|
rule := NewRule()
|
|
rule.UIDRange = NewRuleUIDRange(uid, uid)
|
|
rule.Table = testtable
|
|
if err := RuleAdd(rule); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
dstIP := net.IPv4(10, 1, 1, 1)
|
|
|
|
// check getting route without UID option
|
|
routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{UID: nil})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// current uid is outside uidrange; rule does not apply; lookup main table
|
|
if len(routes) != 1 || !routes[0].Gw.Equal(gw1) {
|
|
t.Fatal(routes)
|
|
}
|
|
|
|
// check getting route with UID option
|
|
routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{UID: &uid})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// option uid is within uidrange; rule applies; lookup test table
|
|
if len(routes) != 1 || !routes[0].Gw.Equal(gw2) {
|
|
t.Fatal(routes)
|
|
}
|
|
}
|
|
|
|
func TestRouteFWMarkOption(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// setup eth0 so that network is reachable
|
|
err := LinkAdd(&Dummy{LinkAttrs{Name: "eth0"}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
link, err := LinkByName("eth0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = LinkSetUp(link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
addr := &Addr{
|
|
IPNet: &net.IPNet{
|
|
IP: net.IPv4(192, 168, 1, 1),
|
|
Mask: net.CIDRMask(16, 32),
|
|
},
|
|
}
|
|
if err = AddrAdd(link, addr); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// a table different than unix.RT_TABLE_MAIN
|
|
testTable0 := 254
|
|
testTable1 := 1000
|
|
testTable2 := 1001
|
|
|
|
gw0 := net.IPv4(192, 168, 1, 254)
|
|
gw1 := net.IPv4(192, 168, 2, 254)
|
|
gw2 := net.IPv4(192, 168, 3, 254)
|
|
|
|
// add default route via gw0 (in main route table by default)
|
|
defaultRouteMain := Route{
|
|
Dst: nil,
|
|
Gw: gw0,
|
|
Table: testTable0,
|
|
}
|
|
if err := RouteAdd(&defaultRouteMain); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// add default route via gw1 in test route table
|
|
defaultRouteTest1 := Route{
|
|
Dst: nil,
|
|
Gw: gw1,
|
|
Table: testTable1,
|
|
}
|
|
if err := RouteAdd(&defaultRouteTest1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// add default route via gw2 in test route table
|
|
defaultRouteTest2 := Route{
|
|
Dst: nil,
|
|
Gw: gw2,
|
|
Table: testTable2,
|
|
}
|
|
if err := RouteAdd(&defaultRouteTest2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// check the routes are in different tables
|
|
routes, err := RouteListFiltered(FAMILY_V4, &Route{
|
|
Dst: nil,
|
|
Table: unix.RT_TABLE_UNSPEC,
|
|
}, RT_FILTER_DST|RT_FILTER_TABLE)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 3 || routes[0].Table == routes[1].Table || routes[1].Table == routes[2].Table ||
|
|
routes[0].Table == routes[2].Table {
|
|
t.Fatal("Routes not added properly")
|
|
}
|
|
|
|
// add a rule that fwmark match should result in route lookup of test table
|
|
fwmark1 := uint32(0xAFFFFFFF)
|
|
fwmark2 := uint32(0xBFFFFFFF)
|
|
|
|
rule := NewRule()
|
|
rule.Mark = fwmark1
|
|
rule.Mask = &[]uint32{0xFFFFFFFF}[0]
|
|
|
|
rule.Table = testTable1
|
|
if err := RuleAdd(rule); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rule = NewRule()
|
|
rule.Mark = fwmark2
|
|
rule.Mask = &[]uint32{0xFFFFFFFF}[0]
|
|
rule.Table = testTable2
|
|
if err := RuleAdd(rule); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rules, err := RuleListFiltered(FAMILY_V4, &Rule{Mark: fwmark1}, RT_FILTER_MARK)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(rules) != 1 || rules[0].Table != testTable1 || rules[0].Mark != fwmark1 {
|
|
t.Fatal("Rules not added properly")
|
|
}
|
|
|
|
rules, err = RuleListFiltered(FAMILY_V4, &Rule{Mark: fwmark2}, RT_FILTER_MARK)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(rules) != 1 || rules[0].Table != testTable2 || rules[0].Mark != fwmark2 {
|
|
t.Fatal("Rules not added properly")
|
|
}
|
|
|
|
dstIP := net.IPv4(10, 1, 1, 1)
|
|
|
|
// check getting route without FWMark option
|
|
routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 || !routes[0].Gw.Equal(gw0) {
|
|
t.Fatal(routes)
|
|
}
|
|
|
|
// check getting route with FWMark option
|
|
routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{Mark: fwmark1})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 || !routes[0].Gw.Equal(gw1) {
|
|
t.Fatal(routes)
|
|
}
|
|
|
|
// check getting route with FWMark option
|
|
routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{Mark: fwmark2})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 || !routes[0].Gw.Equal(gw2) {
|
|
t.Fatal(routes)
|
|
}
|
|
}
|
|
|
|
func TestRouteGetFIBMatchOption(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
err := LinkAdd(&Dummy{LinkAttrs{Name: "eth0"}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
link, err := LinkByName("eth0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = LinkSetUp(link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
addr := &Addr{
|
|
IPNet: &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 2),
|
|
Mask: net.CIDRMask(24, 32),
|
|
},
|
|
}
|
|
if err = AddrAdd(link, addr); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
route := &Route{
|
|
LinkIndex: link.Attrs().Index,
|
|
Gw: net.IPv4(192, 168, 1, 1),
|
|
Dst: &net.IPNet{
|
|
IP: net.IPv4(192, 168, 2, 0),
|
|
Mask: net.CIDRMask(24, 32),
|
|
},
|
|
Flags: int(FLAG_ONLINK),
|
|
}
|
|
|
|
err = RouteAdd(route)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
routes, err := RouteGetWithOptions(net.IPv4(192, 168, 2, 1), &RouteGetOptions{FIBMatch: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(routes) != 1 {
|
|
t.Fatalf("More than one route matched %v", routes)
|
|
}
|
|
|
|
if len(routes[0].ListFlags()) != 1 {
|
|
t.Fatalf("More than one route flag returned %v", routes[0].ListFlags())
|
|
}
|
|
|
|
flag := routes[0].ListFlags()[0]
|
|
if flag != "onlink" {
|
|
t.Fatalf("Unexpected flag %s returned", flag)
|
|
}
|
|
}
|
|
|
|
func TestRouteNHID(t *testing.T) {
|
|
t.Cleanup(setUpNetlinkTest(t))
|
|
|
|
// create dummy interface
|
|
if err := LinkAdd(&Dummy{LinkAttrs: LinkAttrs{Name: "dummy0"}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// get dummy interface
|
|
link0, err := LinkByName("dummy0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// bring the interface up
|
|
if err = LinkSetUp(link0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// add a new IPv6 link-local nexthop
|
|
nh := &Nexthop{
|
|
ID: 1,
|
|
OIF: uint32(link0.Attrs().Index),
|
|
Gateway: net.ParseIP("fe80::1"),
|
|
}
|
|
if err = NexthopAdd(nh); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// IPv4 prefix with IPv6 link local nexthop
|
|
_, dst, err := net.ParseCIDR("10.0.0.0/24")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
route := Route{
|
|
Dst: dst,
|
|
NHID: nh.ID,
|
|
}
|
|
if err = RouteAdd(&route); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Ensure we can retrieve the route we just added
|
|
routes, err := RouteListFiltered(FAMILY_V4, &Route{Dst: dst}, RT_FILTER_DST)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 {
|
|
t.Fatalf("Expected 1 route, got %d", len(routes))
|
|
}
|
|
|
|
if routes[0].NHID != nh.ID {
|
|
t.Fatalf("Expected route NHID %d, got %d", nh.ID, routes[0].NHID)
|
|
}
|
|
}
|