diff --git a/route.go b/route.go index 1b4555d5..2f9df19c 100644 --- a/route.go +++ b/route.go @@ -45,6 +45,17 @@ type Encap interface { Equal(Encap) bool } +type RouteCacheInfo struct { + Users uint32 + Age uint32 + Expires int32 + Error uint32 + Used uint32 + Id uint32 + Ts uint32 + Tsage uint32 +} + //Protocol describe what was the originator of the route type RouteProtocol int @@ -85,6 +96,8 @@ type Route struct { QuickACK int Congctl string FastOpenNoCookie int + Expires int + CacheInfo *RouteCacheInfo } func (r Route) String() string { @@ -115,6 +128,9 @@ func (r Route) String() string { elems = append(elems, fmt.Sprintf("Flags: %s", r.ListFlags())) elems = append(elems, fmt.Sprintf("Table: %d", r.Table)) elems = append(elems, fmt.Sprintf("Realm: %d", r.Realm)) + if r.Expires != 0 { + elems = append(elems, fmt.Sprintf("Expires: %dsec", r.Expires)) + } return fmt.Sprintf("{%s}", strings.Join(elems, " ")) } diff --git a/route_linux.go b/route_linux.go index 28a132a2..81d286b7 100644 --- a/route_linux.go +++ b/route_linux.go @@ -1068,6 +1068,12 @@ func (h *Handle) prepareRouteReq(route *Route, req *nl.NetlinkRequest, msg *nl.R msg.Type = uint8(route.Type) } + if route.Expires > 0 { + b := make([]byte, 4) + native.PutUint32(b, uint32(route.Expires)) + rtAttrs = append(rtAttrs, nl.NewRtAttr(unix.RTA_EXPIRES, b)) + } + var metrics []*nl.RtAttr if route.MTU > 0 { b := nl.Uint32Attr(uint32(route.MTU)) @@ -1294,6 +1300,25 @@ func (h *Handle) RouteListFilteredIter(family int, filter *Route, filterMask uin return executeErr } +// deserializeRouteCacheInfo decodes a RTA_CACHEINFO attribute into a RouteCacheInfo struct +func deserializeRouteCacheInfo(b []byte) (*RouteCacheInfo, error) { + if len(b) != 32 { + return nil, unix.EINVAL + } + + e := nl.NativeEndian() + return &RouteCacheInfo{ + e.Uint32(b), + e.Uint32(b[4:]), + int32(e.Uint32(b[8:])), + e.Uint32(b[12:]), + e.Uint32(b[16:]), + e.Uint32(b[20:]), + e.Uint32(b[24:]), + e.Uint32(b[28:]), + }, nil +} + // deserializeRoute decodes a binary netlink message into a Route struct func deserializeRoute(m []byte) (Route, error) { msg := nl.DeserializeRtMsg(m) @@ -1337,6 +1362,12 @@ func deserializeRoute(m []byte) (Route, error) { route.ILinkIndex = int(native.Uint32(attr.Value[0:4])) case unix.RTA_PRIORITY: route.Priority = int(native.Uint32(attr.Value[0:4])) + case unix.RTA_CACHEINFO: + route.CacheInfo, err = deserializeRouteCacheInfo(attr.Value) + if err != nil { + return route, err + } + route.Expires = int(route.CacheInfo.Expires) / 100 case unix.RTA_FLOW: route.Realm = int(native.Uint32(attr.Value[0:4])) case unix.RTA_TABLE: diff --git a/route_test.go b/route_test.go index 7e5e6b0e..f4015eb4 100644 --- a/route_test.go +++ b/route_test.go @@ -192,7 +192,7 @@ func TestRoute6AddDel(t *testing.T) { IP: net.ParseIP("2001:db8::0"), Mask: net.CIDRMask(64, 128), } - route := Route{LinkIndex: link.Attrs().Index, Dst: dst} + route := Route{LinkIndex: link.Attrs().Index, Dst: dst, Expires: 10} if err := RouteAdd(&route); err != nil { t.Fatal(err) } @@ -204,6 +204,25 @@ func TestRoute6AddDel(t *testing.T) { t.Fatal("Route not added properly") } + // route expiry is supported by kernel 4.4+ + k, m, err := KernelVersion() + if err != nil { + t.Fatal(err) + } + if k > 4 || (k == 4 && m > 4) { + foundExpires := false + for _, route := range routes { + if route.Dst.IP.Equal(dst.IP) { + if route.Expires > 0 && route.Expires <= 10 { + foundExpires = true + } + } + } + if !foundExpires { + t.Fatal("Route 'expires' not set") + } + } + dstIP := net.ParseIP("2001:db8::1") routeToDstIP, err := RouteGet(dstIP) if err != nil {