mirror of
https://github.com/vishvananda/netlink
synced 2025-01-28 02:13:41 +00:00
b54f85093f
Allows for listing large numbers of routes without buffering the whole list in memory at once. Add benchmarks for RouteListFiltered variants.
2578 lines
56 KiB
Go
2578 lines
56 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
package netlink
|
|
|
|
import (
|
|
"net"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/vishvananda/netlink/nl"
|
|
"github.com/vishvananda/netns"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func TestRouteAddDel(t *testing.T) {
|
|
tearDown := setUpNetlinkTest(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)
|
|
}
|
|
|
|
// 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) {
|
|
tearDown := setUpNetlinkTest(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
|
|
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}
|
|
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")
|
|
}
|
|
|
|
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) {
|
|
tearDown := setUpNetlinkTest(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)
|
|
}
|
|
|
|
// 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) {
|
|
tearDown := setUpNetlinkTest(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)
|
|
}
|
|
|
|
// 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) {
|
|
tearDown := setUpNetlinkTest(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)
|
|
}
|
|
|
|
// 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) {
|
|
tearDown := setUpNetlinkTest(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)
|
|
}
|
|
|
|
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) {
|
|
tearDown := setUpNetlinkTest(t)
|
|
defer tearDown()
|
|
|
|
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) {
|
|
tearDown := setUpNetlinkTest(t)
|
|
defer tearDown()
|
|
|
|
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) {
|
|
tearDown := setUpNetlinkTest(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)
|
|
}
|
|
|
|
// 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) {
|
|
tearDown := setUpNetlinkTest(t)
|
|
defer tearDown()
|
|
|
|
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) {
|
|
tearDown := setUpNetlinkTest(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)
|
|
}
|
|
|
|
// 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) {
|
|
tearDown := setUpNetlinkTest(b)
|
|
defer tearDown()
|
|
|
|
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) {
|
|
tearDown := setUpNetlinkTest(b)
|
|
defer tearDown()
|
|
|
|
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) {
|
|
tearDown := setUpNetlinkTest(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)
|
|
}
|
|
|
|
// 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) {
|
|
tearDown := setUpNetlinkTest(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)
|
|
}
|
|
|
|
// 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) {
|
|
tearDown := setUpNetlinkTest(t)
|
|
defer tearDown()
|
|
|
|
// 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")
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
}
|
|
|
|
func TestFilterDefaultRoute(t *testing.T) {
|
|
tearDown := setUpNetlinkTest(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)
|
|
}
|
|
|
|
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) {
|
|
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")
|
|
}
|
|
|
|
}
|
|
|
|
func TestIP6tnlRouteAddDel(t *testing.T) {
|
|
_, err := RouteList(nil, FAMILY_V4)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tearDown := setUpNetlinkTest(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)
|
|
}
|
|
|
|
_, 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_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_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) {
|
|
if os.Getenv("CI") == "true" {
|
|
t.Skipf("Fails in CI with: route_test.go:*: Invalid Type. SEG6_IPTUN_MODE_INLINE routes not added properly")
|
|
}
|
|
// add/del routes with LWTUNNEL_SEG6 to/from loopback interface.
|
|
// Test both seg6 modes: encap (IPv4) & inline (IPv6).
|
|
tearDown := setUpSEG6NetlinkTest(t)
|
|
defer tearDown()
|
|
|
|
// get loopback interface and bring it up
|
|
link, err := LinkByName("lo")
|
|
if err != nil {
|
|
t.Fatal(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("::")) // inline requires "::"
|
|
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
|
|
routes, err := RouteList(link, FAMILY_V6)
|
|
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
|
|
routes, err = RouteList(link, FAMILY_V4)
|
|
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)
|
|
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.
|
|
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)
|
|
}
|
|
|
|
tearDown := setUpNetlinkTest(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)
|
|
}
|
|
|
|
// 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 TestRouteViaAddDel(t *testing.T) {
|
|
minKernelRequired(t, 5, 4)
|
|
tearDown := setUpNetlinkTest(t)
|
|
defer tearDown()
|
|
|
|
_, 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) {
|
|
tearDown := setUpNetlinkTest(t)
|
|
defer tearDown()
|
|
|
|
// 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) {
|
|
tearDown := setUpNetlinkTest(t)
|
|
defer tearDown()
|
|
|
|
// 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 fwmark match should result in route lookup of test table
|
|
fwmark := 1000
|
|
|
|
rule := NewRule()
|
|
rule.Mark = fwmark
|
|
rule.Mask = 0xFFFFFFFF
|
|
rule.Table = testtable
|
|
if err := RuleAdd(rule); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
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(gw1) {
|
|
t.Fatal(routes)
|
|
}
|
|
|
|
// check getting route with FWMark option
|
|
routes, err = RouteGetWithOptions(dstIP, &RouteGetOptions{Mark: fwmark})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(routes) != 1 || !routes[0].Gw.Equal(gw2) {
|
|
t.Fatal(routes)
|
|
}
|
|
}
|
|
|
|
func TestRouteGetFIBMatchOption(t *testing.T) {
|
|
tearDown := setUpNetlinkTest(t)
|
|
defer tearDown()
|
|
|
|
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)
|
|
}
|
|
}
|