From 0a246fab80581351309cdfe39ffeeffa00f811b1 Mon Sep 17 00:00:00 2001 From: Ian Lewis Date: Wed, 31 Jul 2019 20:29:07 -0700 Subject: Basic support for 'ip route' Implements support for RTM_GETROUTE requests for netlink sockets. Fixes #507 PiperOrigin-RevId: 261051045 --- pkg/sentry/inet/inet.go | 49 +++++++++++++++-- pkg/sentry/inet/test_stack.go | 6 +++ pkg/sentry/socket/epsocket/epsocket.go | 8 +-- pkg/sentry/socket/epsocket/stack.go | 45 ++++++++++++++++ pkg/sentry/socket/hostinet/stack.go | 84 ++++++++++++++++++++++++++++- pkg/sentry/socket/netlink/route/protocol.go | 62 ++++++++++++++++++++- pkg/sentry/socket/rpcinet/stack.go | 28 +++++++++- 7 files changed, 268 insertions(+), 14 deletions(-) (limited to 'pkg/sentry') diff --git a/pkg/sentry/inet/inet.go b/pkg/sentry/inet/inet.go index 5b75a4a06..60d6dfb93 100644 --- a/pkg/sentry/inet/inet.go +++ b/pkg/sentry/inet/inet.go @@ -52,12 +52,13 @@ type Stack interface { // Statistics reports stack statistics. Statistics(stat interface{}, arg string) error + + // RouteTable returns the network stack's route table. + RouteTable() []Route } // Interface contains information about a network interface. type Interface struct { - // Keep these fields sorted in the order they appear in rtnetlink(7). - // DeviceType is the device type, a Linux ARPHRD_* constant. DeviceType uint16 @@ -77,8 +78,6 @@ type Interface struct { // InterfaceAddr contains information about a network interface address. type InterfaceAddr struct { - // Keep these fields sorted in the order they appear in rtnetlink(7). - // Family is the address family, a Linux AF_* constant. Family uint8 @@ -109,3 +108,45 @@ type TCPBufferSize struct { // StatDev describes one line of /proc/net/dev, i.e., stats for one network // interface. type StatDev [16]uint64 + +// Route contains information about a network route. +type Route struct { + // Family is the address family, a Linux AF_* constant. + Family uint8 + + // DstLen is the length of the destination address. + DstLen uint8 + + // SrcLen is the length of the source address. + SrcLen uint8 + + // TOS is the Type of Service filter. + TOS uint8 + + // Table is the routing table ID. + Table uint8 + + // Protocol is the route origin, a Linux RTPROT_* constant. + Protocol uint8 + + // Scope is the distance to destination, a Linux RT_SCOPE_* constant. + Scope uint8 + + // Type is the route origin, a Linux RTN_* constant. + Type uint8 + + // Flags are route flags. See rtnetlink(7) under "rtm_flags". + Flags uint32 + + // DstAddr is the route destination address (RTA_DST). + DstAddr []byte + + // SrcAddr is the route source address (RTA_SRC). + SrcAddr []byte + + // OutputInterface is the output interface index (RTA_OIF). + OutputInterface int32 + + // GatewayAddr is the route gateway address (RTA_GATEWAY). + GatewayAddr []byte +} diff --git a/pkg/sentry/inet/test_stack.go b/pkg/sentry/inet/test_stack.go index 75f9e7a77..57d5510f0 100644 --- a/pkg/sentry/inet/test_stack.go +++ b/pkg/sentry/inet/test_stack.go @@ -18,6 +18,7 @@ package inet type TestStack struct { InterfacesMap map[int32]Interface InterfaceAddrsMap map[int32][]InterfaceAddr + RouteList []Route SupportsIPv6Flag bool TCPRecvBufSize TCPBufferSize TCPSendBufSize TCPBufferSize @@ -86,3 +87,8 @@ func (s *TestStack) SetTCPSACKEnabled(enabled bool) error { func (s *TestStack) Statistics(stat interface{}, arg string) error { return nil } + +// RouteTable implements Stack.RouteTable. +func (s *TestStack) RouteTable() []Route { + return s.RouteList +} diff --git a/pkg/sentry/socket/epsocket/epsocket.go b/pkg/sentry/socket/epsocket/epsocket.go index e57aed927..0f483faa8 100644 --- a/pkg/sentry/socket/epsocket/epsocket.go +++ b/pkg/sentry/socket/epsocket/epsocket.go @@ -2252,19 +2252,19 @@ func interfaceIoctl(ctx context.Context, io usermem.IO, arg int, ifr *linux.IFRe case syscall.SIOCGIFMAP: // Gets the hardware parameters of the device. - // TODO(b/71872867): Implement. + // TODO(gvisor.dev/issue/505): Implement. case syscall.SIOCGIFTXQLEN: // Gets the transmit queue length of the device. - // TODO(b/71872867): Implement. + // TODO(gvisor.dev/issue/505): Implement. case syscall.SIOCGIFDSTADDR: // Gets the destination address of a point-to-point device. - // TODO(b/71872867): Implement. + // TODO(gvisor.dev/issue/505): Implement. case syscall.SIOCGIFBRDADDR: // Gets the broadcast address of a device. - // TODO(b/71872867): Implement. + // TODO(gvisor.dev/issue/505): Implement. case syscall.SIOCGIFNETMASK: // Gets the network mask of a device. diff --git a/pkg/sentry/socket/epsocket/stack.go b/pkg/sentry/socket/epsocket/stack.go index 8fe489c0e..27774be33 100644 --- a/pkg/sentry/socket/epsocket/stack.go +++ b/pkg/sentry/socket/epsocket/stack.go @@ -19,6 +19,8 @@ import ( "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sentry/inet" "gvisor.dev/gvisor/pkg/syserr" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" "gvisor.dev/gvisor/pkg/tcpip/stack" @@ -143,3 +145,46 @@ func (s *Stack) SetTCPSACKEnabled(enabled bool) error { func (s *Stack) Statistics(stat interface{}, arg string) error { return syserr.ErrEndpointOperation.ToError() } + +// RouteTable implements inet.Stack.RouteTable. +func (s *Stack) RouteTable() []inet.Route { + var routeTable []inet.Route + + for _, rt := range s.Stack.GetRouteTable() { + var family uint8 + switch len(rt.Destination) { + case header.IPv4AddressSize: + family = linux.AF_INET + case header.IPv6AddressSize: + family = linux.AF_INET6 + default: + log.Warningf("Unknown network protocol in route %+v", rt) + continue + } + + dstSubnet, err := tcpip.NewSubnet(rt.Destination, rt.Mask) + if err != nil { + log.Warningf("Invalid destination & mask in route: %s(%s): %v", rt.Destination, rt.Mask, err) + continue + } + routeTable = append(routeTable, inet.Route{ + Family: family, + DstLen: uint8(dstSubnet.Prefix()), // The CIDR prefix for the destination. + + // Always return unspecified protocol since we have no notion of + // protocol for routes. + Protocol: linux.RTPROT_UNSPEC, + // Set statically to LINK scope for now. + // + // TODO(gvisor.dev/issue/595): Set scope for routes. + Scope: linux.RT_SCOPE_LINK, + Type: linux.RTN_UNICAST, + + DstAddr: []byte(rt.Destination), + OutputInterface: int32(rt.NIC), + GatewayAddr: []byte(rt.Gateway), + }) + } + + return routeTable +} diff --git a/pkg/sentry/socket/hostinet/stack.go b/pkg/sentry/socket/hostinet/stack.go index cc1f66fa1..99b7a1e2b 100644 --- a/pkg/sentry/socket/hostinet/stack.go +++ b/pkg/sentry/socket/hostinet/stack.go @@ -46,6 +46,7 @@ type Stack struct { // Stack is immutable. interfaces map[int32]inet.Interface interfaceAddrs map[int32][]inet.InterfaceAddr + routes []inet.Route supportsIPv6 bool tcpRecvBufSize inet.TCPBufferSize tcpSendBufSize inet.TCPBufferSize @@ -66,6 +67,10 @@ func (s *Stack) Configure() error { return err } + if err := addHostRoutes(s); err != nil { + return err + } + if _, err := os.Stat("/proc/net/if_inet6"); err == nil { s.supportsIPv6 = true } @@ -161,6 +166,54 @@ func ExtractHostInterfaces(links []syscall.NetlinkMessage, addrs []syscall.Netli return nil } +// ExtractHostRoutes populates the given routes slice with the data from the +// host route table. +func ExtractHostRoutes(routeMsgs []syscall.NetlinkMessage) ([]inet.Route, error) { + var routes []inet.Route + for _, routeMsg := range routeMsgs { + if routeMsg.Header.Type != syscall.RTM_NEWROUTE { + continue + } + + var ifRoute syscall.RtMsg + binary.Unmarshal(routeMsg.Data[:syscall.SizeofRtMsg], usermem.ByteOrder, &ifRoute) + inetRoute := inet.Route{ + Family: ifRoute.Family, + DstLen: ifRoute.Dst_len, + SrcLen: ifRoute.Src_len, + TOS: ifRoute.Tos, + Table: ifRoute.Table, + Protocol: ifRoute.Protocol, + Scope: ifRoute.Scope, + Type: ifRoute.Type, + Flags: ifRoute.Flags, + } + + // Not clearly documented: syscall.ParseNetlinkRouteAttr will check the + // syscall.NetlinkMessage.Header.Type and skip the struct rtmsg + // accordingly. + attrs, err := syscall.ParseNetlinkRouteAttr(&routeMsg) + if err != nil { + return nil, fmt.Errorf("RTM_GETROUTE returned RTM_NEWROUTE message with invalid rtattrs: %v", err) + } + + for _, attr := range attrs { + switch attr.Attr.Type { + case syscall.RTA_DST: + inetRoute.DstAddr = attr.Value + case syscall.RTA_SRC: + inetRoute.SrcAddr = attr.Value + case syscall.RTA_OIF: + inetRoute.GatewayAddr = attr.Value + } + } + + routes = append(routes, inetRoute) + } + + return routes, nil +} + func addHostInterfaces(s *Stack) error { links, err := doNetlinkRouteRequest(syscall.RTM_GETLINK) if err != nil { @@ -175,6 +228,20 @@ func addHostInterfaces(s *Stack) error { return ExtractHostInterfaces(links, addrs, s.interfaces, s.interfaceAddrs) } +func addHostRoutes(s *Stack) error { + routes, err := doNetlinkRouteRequest(syscall.RTM_GETROUTE) + if err != nil { + return fmt.Errorf("RTM_GETROUTE failed: %v", err) + } + + s.routes, err = ExtractHostRoutes(routes) + if err != nil { + return err + } + + return nil +} + func doNetlinkRouteRequest(req int) ([]syscall.NetlinkMessage, error) { data, err := syscall.NetlinkRIB(req, syscall.AF_UNSPEC) if err != nil { @@ -202,12 +269,20 @@ func readTCPBufferSizeFile(filename string) (inet.TCPBufferSize, error) { // Interfaces implements inet.Stack.Interfaces. func (s *Stack) Interfaces() map[int32]inet.Interface { - return s.interfaces + interfaces := make(map[int32]inet.Interface) + for k, v := range s.interfaces { + interfaces[k] = v + } + return interfaces } // InterfaceAddrs implements inet.Stack.InterfaceAddrs. func (s *Stack) InterfaceAddrs() map[int32][]inet.InterfaceAddr { - return s.interfaceAddrs + addrs := make(map[int32][]inet.InterfaceAddr) + for k, v := range s.interfaceAddrs { + addrs[k] = append([]inet.InterfaceAddr(nil), v...) + } + return addrs } // SupportsIPv6 implements inet.Stack.SupportsIPv6. @@ -249,3 +324,8 @@ func (s *Stack) SetTCPSACKEnabled(enabled bool) error { func (s *Stack) Statistics(stat interface{}, arg string) error { return syserror.EOPNOTSUPP } + +// RouteTable implements inet.Stack.RouteTable. +func (s *Stack) RouteTable() []inet.Route { + return append([]inet.Route(nil), s.routes...) +} diff --git a/pkg/sentry/socket/netlink/route/protocol.go b/pkg/sentry/socket/netlink/route/protocol.go index fb1ff329c..cc70ac237 100644 --- a/pkg/sentry/socket/netlink/route/protocol.go +++ b/pkg/sentry/socket/netlink/route/protocol.go @@ -110,7 +110,7 @@ func (p *Protocol) dumpLinks(ctx context.Context, hdr linux.NetlinkMessageHeader m.PutAttr(linux.IFLA_ADDRESS, mac) m.PutAttr(linux.IFLA_BROADCAST, brd) - // TODO(b/68878065): There are many more attributes. + // TODO(gvisor.dev/issue/578): There are many more attributes. } return nil @@ -151,13 +151,69 @@ func (p *Protocol) dumpAddrs(ctx context.Context, hdr linux.NetlinkMessageHeader m.PutAttr(linux.IFA_ADDRESS, []byte(a.Addr)) - // TODO(b/68878065): There are many more attributes. + // TODO(gvisor.dev/issue/578): There are many more attributes. } } return nil } +// dumpRoutes handles RTM_GETROUTE + NLM_F_DUMP requests. +func (p *Protocol) dumpRoutes(ctx context.Context, hdr linux.NetlinkMessageHeader, data []byte, ms *netlink.MessageSet) *syserr.Error { + // RTM_GETROUTE dump requests need not contain anything more than the + // netlink header and 1 byte protocol family common to all + // NETLINK_ROUTE requests. + + // We always send back an NLMSG_DONE. + ms.Multi = true + + stack := inet.StackFromContext(ctx) + if stack == nil { + // No network routes. + return nil + } + + for _, rt := range stack.RouteTable() { + m := ms.AddMessage(linux.NetlinkMessageHeader{ + Type: linux.RTM_NEWROUTE, + }) + + m.Put(linux.RouteMessage{ + Family: rt.Family, + DstLen: rt.DstLen, + SrcLen: rt.SrcLen, + TOS: rt.TOS, + + // Always return the main table since we don't have multiple + // routing tables. + Table: linux.RT_TABLE_MAIN, + Protocol: rt.Protocol, + Scope: rt.Scope, + Type: rt.Type, + + Flags: rt.Flags, + }) + + m.PutAttr(254, []byte{123}) + if rt.DstLen > 0 { + m.PutAttr(linux.RTA_DST, rt.DstAddr) + } + if rt.SrcLen > 0 { + m.PutAttr(linux.RTA_SRC, rt.SrcAddr) + } + if rt.OutputInterface != 0 { + m.PutAttr(linux.RTA_OIF, rt.OutputInterface) + } + if len(rt.GatewayAddr) > 0 { + m.PutAttr(linux.RTA_GATEWAY, rt.GatewayAddr) + } + + // TODO(gvisor.dev/issue/578): There are many more attributes. + } + + return nil +} + // ProcessMessage implements netlink.Protocol.ProcessMessage. func (p *Protocol) ProcessMessage(ctx context.Context, hdr linux.NetlinkMessageHeader, data []byte, ms *netlink.MessageSet) *syserr.Error { // All messages start with a 1 byte protocol family. @@ -186,6 +242,8 @@ func (p *Protocol) ProcessMessage(ctx context.Context, hdr linux.NetlinkMessageH return p.dumpLinks(ctx, hdr, data, ms) case linux.RTM_GETADDR: return p.dumpAddrs(ctx, hdr, data, ms) + case linux.RTM_GETROUTE: + return p.dumpRoutes(ctx, hdr, data, ms) default: return syserr.ErrNotSupported } diff --git a/pkg/sentry/socket/rpcinet/stack.go b/pkg/sentry/socket/rpcinet/stack.go index 49bd3a220..d18305589 100644 --- a/pkg/sentry/socket/rpcinet/stack.go +++ b/pkg/sentry/socket/rpcinet/stack.go @@ -30,6 +30,7 @@ import ( type Stack struct { interfaces map[int32]inet.Interface interfaceAddrs map[int32][]inet.InterfaceAddr + routes []inet.Route rpcConn *conn.RPCConnection notifier *notifier.Notifier } @@ -69,6 +70,16 @@ func NewStack(fd int32) (*Stack, error) { return nil, e } + routes, err := stack.DoNetlinkRouteRequest(syscall.RTM_GETROUTE) + if err != nil { + return nil, fmt.Errorf("RTM_GETROUTE failed: %v", err) + } + + stack.routes, e = hostinet.ExtractHostRoutes(routes) + if e != nil { + return nil, e + } + return stack, nil } @@ -89,12 +100,20 @@ func (s *Stack) RPCWriteFile(path string, data []byte) (int64, *syserr.Error) { // Interfaces implements inet.Stack.Interfaces. func (s *Stack) Interfaces() map[int32]inet.Interface { - return s.interfaces + interfaces := make(map[int32]inet.Interface) + for k, v := range s.interfaces { + interfaces[k] = v + } + return interfaces } // InterfaceAddrs implements inet.Stack.InterfaceAddrs. func (s *Stack) InterfaceAddrs() map[int32][]inet.InterfaceAddr { - return s.interfaceAddrs + addrs := make(map[int32][]inet.InterfaceAddr) + for k, v := range s.interfaceAddrs { + addrs[k] = append([]inet.InterfaceAddr(nil), v...) + } + return addrs } // SupportsIPv6 implements inet.Stack.SupportsIPv6. @@ -138,3 +157,8 @@ func (s *Stack) SetTCPSACKEnabled(enabled bool) error { func (s *Stack) Statistics(stat interface{}, arg string) error { return syserr.ErrEndpointOperation.ToError() } + +// RouteTable implements inet.Stack.RouteTable. +func (s *Stack) RouteTable() []inet.Route { + return append([]inet.Route(nil), s.routes...) +} -- cgit v1.2.3