diff --git a/route_linux.go b/route_linux.go index 267bb7f..02f4de3 100644 --- a/route_linux.go +++ b/route_linux.go @@ -1298,6 +1298,7 @@ type RouteGetOptions struct { Oif string VrfName string SrcAddr net.IP + UID *uint32 } // RouteGetWithOptions gets a route to a specific destination from the host system. @@ -1384,6 +1385,13 @@ func (h *Handle) RouteGetWithOptions(destination net.IP, options *RouteGetOption req.AddData(nl.NewRtAttr(unix.RTA_SRC, srcAddr)) } + + if options.UID != nil { + uid := *options.UID + b := make([]byte, 4) + native.PutUint32(b, uid) + req.AddData(nl.NewRtAttr(unix.RTA_UID, b)) + } } msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWROUTE) diff --git a/route_test.go b/route_test.go index a8f3e50..9accbf3 100644 --- a/route_test.go +++ b/route_test.go @@ -1644,3 +1644,99 @@ func TestRouteViaAddDel(t *testing.T) { 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) + } +}