Basic support for 'ip route'

Implements support for RTM_GETROUTE requests for netlink sockets.

Fixes #507

PiperOrigin-RevId: 261051045
This commit is contained in:
Ian Lewis 2019-07-31 20:29:07 -07:00 committed by gVisor bot
parent 77833ece3b
commit 0a246fab80
9 changed files with 477 additions and 14 deletions

View File

@ -189,3 +189,139 @@ const (
const (
ARPHRD_LOOPBACK = 772
)
// RouteMessage struct rtmsg, from uapi/linux/rtnetlink.h.
type RouteMessage struct {
Family uint8
DstLen uint8
SrcLen uint8
TOS uint8
Table uint8
Protocol uint8
Scope uint8
Type uint8
Flags uint32
}
// Route types, from uapi/linux/rtnetlink.h.
const (
// RTN_UNSPEC represents an unspecified route type.
RTN_UNSPEC = 0
// RTN_UNICAST represents a unicast route.
RTN_UNICAST = 1
// RTN_LOCAL represents a route that is accepted locally.
RTN_LOCAL = 2
// RTN_BROADCAST represents a broadcast route (Traffic is accepted locally
// as broadcast, and sent as broadcast).
RTN_BROADCAST = 3
// RTN_ANYCAST represents a anycast route (Traffic is accepted locally as
// broadcast but sent as unicast).
RTN_ANYCAST = 6
// RTN_MULTICAST represents a multicast route.
RTN_MULTICAST = 5
// RTN_BLACKHOLE represents a route where all traffic is dropped.
RTN_BLACKHOLE = 6
// RTN_UNREACHABLE represents a route where the destination is unreachable.
RTN_UNREACHABLE = 7
RTN_PROHIBIT = 8
RTN_THROW = 9
RTN_NAT = 10
RTN_XRESOLVE = 11
)
// Route protocols/origins, from uapi/linux/rtnetlink.h.
const (
RTPROT_UNSPEC = 0
RTPROT_REDIRECT = 1
RTPROT_KERNEL = 2
RTPROT_BOOT = 3
RTPROT_STATIC = 4
RTPROT_GATED = 8
RTPROT_RA = 9
RTPROT_MRT = 10
RTPROT_ZEBRA = 11
RTPROT_BIRD = 12
RTPROT_DNROUTED = 13
RTPROT_XORP = 14
RTPROT_NTK = 15
RTPROT_DHCP = 16
RTPROT_MROUTED = 17
RTPROT_BABEL = 42
RTPROT_BGP = 186
RTPROT_ISIS = 187
RTPROT_OSPF = 188
RTPROT_RIP = 189
RTPROT_EIGRP = 192
)
// Route scopes, from uapi/linux/rtnetlink.h.
const (
RT_SCOPE_UNIVERSE = 0
RT_SCOPE_SITE = 200
RT_SCOPE_LINK = 253
RT_SCOPE_HOST = 254
RT_SCOPE_NOWHERE = 255
)
// Route flags, from uapi/linux/rtnetlink.h.
const (
RTM_F_NOTIFY = 0x100
RTM_F_CLONED = 0x200
RTM_F_EQUALIZE = 0x400
RTM_F_PREFIX = 0x800
RTM_F_LOOKUP_TABLE = 0x1000
RTM_F_FIB_MATCH = 0x2000
)
// Route tables, from uapi/linux/rtnetlink.h.
const (
RT_TABLE_UNSPEC = 0
RT_TABLE_COMPAT = 252
RT_TABLE_DEFAULT = 253
RT_TABLE_MAIN = 254
RT_TABLE_LOCAL = 255
)
// Route attributes, from uapi/linux/rtnetlink.h.
const (
RTA_UNSPEC = 0
RTA_DST = 1
RTA_SRC = 2
RTA_IIF = 3
RTA_OIF = 4
RTA_GATEWAY = 5
RTA_PRIORITY = 6
RTA_PREFSRC = 7
RTA_METRICS = 8
RTA_MULTIPATH = 9
RTA_PROTOINFO = 10
RTA_FLOW = 11
RTA_CACHEINFO = 12
RTA_SESSION = 13
RTA_MP_ALGO = 14
RTA_TABLE = 15
RTA_MARK = 16
RTA_MFC_STATS = 17
RTA_VIA = 18
RTA_NEWDST = 19
RTA_PREF = 20
RTA_ENCAP_TYPE = 21
RTA_ENCAP = 22
RTA_EXPIRES = 23
RTA_PAD = 24
RTA_UID = 25
RTA_TTL_PROPAGATE = 26
RTA_IP_PROTO = 27
RTA_SPORT = 28
RTA_DPORT = 29
)

View File

@ -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
}

View File

@ -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
}

View File

@ -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.

View File

@ -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
}

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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...)
}

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
@ -425,6 +426,78 @@ TEST(NetlinkRouteTest, LookupAll) {
ASSERT_GT(count, 0);
}
// GetRouteDump tests a RTM_GETROUTE + NLM_F_DUMP request.
TEST(NetlinkRouteTest, GetRouteDump) {
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get()));
struct request {
struct nlmsghdr hdr;
struct rtmsg rtm;
};
constexpr uint32_t kSeq = 12345;
struct request req = {};
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETROUTE;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.hdr.nlmsg_seq = kSeq;
req.rtm.rtm_family = AF_UNSPEC;
bool routeFound = false;
bool dstFound = true;
ASSERT_NO_ERRNO(NetlinkRequestResponse(
fd, &req, sizeof(req), [&](const struct nlmsghdr* hdr) {
// Validate the reponse to RTM_GETROUTE + NLM_F_DUMP.
EXPECT_THAT(hdr->nlmsg_type, AnyOf(Eq(RTM_NEWROUTE), Eq(NLMSG_DONE)));
EXPECT_TRUE((hdr->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI)
<< std::hex << hdr->nlmsg_flags;
EXPECT_EQ(hdr->nlmsg_seq, kSeq);
EXPECT_EQ(hdr->nlmsg_pid, port);
// The test should not proceed if it's not a RTM_NEWROUTE message.
if (hdr->nlmsg_type != RTM_NEWROUTE) {
return;
}
// RTM_NEWROUTE contains at least the header and rtmsg.
ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct rtmsg)));
const struct rtmsg* msg =
reinterpret_cast<const struct rtmsg*>(NLMSG_DATA(hdr));
// NOTE: rtmsg fields are char fields.
std::cout << "Found route table=" << static_cast<int>(msg->rtm_table)
<< ", protocol=" << static_cast<int>(msg->rtm_protocol)
<< ", scope=" << static_cast<int>(msg->rtm_scope)
<< ", type=" << static_cast<int>(msg->rtm_type);
int len = RTM_PAYLOAD(hdr);
bool rtDstFound = false;
for (struct rtattr* attr = RTM_RTA(msg); RTA_OK(attr, len);
attr = RTA_NEXT(attr, len)) {
if (attr->rta_type == RTA_DST) {
char address[INET_ADDRSTRLEN] = {};
inet_ntop(AF_INET, RTA_DATA(attr), address, sizeof(address));
std::cout << ", dst=" << address;
rtDstFound = true;
}
}
std::cout << std::endl;
if (msg->rtm_table == RT_TABLE_MAIN) {
routeFound = true;
dstFound = rtDstFound && dstFound;
}
}));
// At least one route found in main route table.
EXPECT_TRUE(routeFound);
// Found RTA_DST for each route in main table.
EXPECT_TRUE(dstFound);
}
} // namespace
} // namespace testing