netlink/route_test.go
Tao Li 8d48f50f55 Added ip6tnlEncap to insert ip6tnl encap route
This commit added support to allow the operation of ip6tnl encapsulation.
It is equivalent to the iproute2 command, e.g., ip route add 192.168.99.0/24 encap ip6 dst 2001:db8:: dev ip6-tunnel
The limitation include that the options field defined in encap nl route attribute is not implenmented yet.
Testcase is included.
2024-01-23 10:47:39 -08:00

2141 lines
46 KiB
Go

//go:build linux
// +build linux
package netlink
import (
"net"
"os"
"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 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")
}
}
// expectNeighUpdate returns whether the expected updated is received within one minute.
func expectRouteUpdate(ch <-chan RouteUpdate, t uint16, dst net.IP) bool {
for {
timeout := time.After(time.Minute)
select {
case update := <-ch:
if update.Type == t &&
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, 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, 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, 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, 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, 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, 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, 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, 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, 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 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 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
cases := []SEG6LocalEncap{
{
Flags: flags_end,
Action: nl.SEG6_LOCAL_ACTION_END,
},
{
Flags: flags_end_x,
Action: nl.SEG6_LOCAL_ACTION_END_X,
In6Addr: net.ParseIP("2001:db8::1"),
},
{
Flags: flags_end_t,
Action: nl.SEG6_LOCAL_ACTION_END_T,
Table: 10,
},
{
Flags: flags_end_dx2,
Action: nl.SEG6_LOCAL_ACTION_END_DX2,
Oif: 20,
},
{
Flags: flags_end_dx6,
Action: nl.SEG6_LOCAL_ACTION_END_DX6,
In6Addr: net.ParseIP("2001:db8::1"),
},
{
Flags: flags_end_dx4,
Action: nl.SEG6_LOCAL_ACTION_END_DX4,
InAddr: net.IPv4(192, 168, 10, 10),
},
{
Flags: flags_end_dt6,
Action: nl.SEG6_LOCAL_ACTION_END_DT6,
Table: 30,
},
{
Flags: flags_end_dt4,
Action: nl.SEG6_LOCAL_ACTION_END_DT4,
Table: 40,
},
{
Flags: flags_end_b6,
Action: nl.SEG6_LOCAL_ACTION_END_B6,
Segments: segs,
},
{
Flags: flags_end_b6_encaps,
Action: nl.SEG6_LOCAL_ACTION_END_B6_ENCAPS,
Segments: segs,
},
}
for i1 := range cases {
for i2 := range cases {
got := cases[i1].Equal(&cases[i2])
expected := i1 == i2
if got != expected {
t.Errorf("Equal(%v,%v) == %s but expected %s",
cases[i1], cases[i2],
strconv.FormatBool(got),
strconv.FormatBool(expected))
}
}
}
}
func TestSEG6RouteAddDel(t *testing.T) {
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)
}
}