Support RTM_NEWADDR and RTM_GETLINK in (rt)netlink.

PiperOrigin-RevId: 293271055
This commit is contained in:
Ting-Yu Wang
2020-02-04 18:05:03 -08:00
committed by gVisor bot
parent b29aeebaf6
commit 665b614e4a
17 changed files with 1031 additions and 171 deletions
+4
View File
@@ -28,6 +28,10 @@ type Stack interface {
// interface indexes to a slice of associated interface address properties.
InterfaceAddrs() map[int32][]InterfaceAddr
// AddInterfaceAddr adds an address to the network interface identified by
// index.
AddInterfaceAddr(idx int32, addr InterfaceAddr) error
// SupportsIPv6 returns true if the stack supports IPv6 connectivity.
SupportsIPv6() bool
+6
View File
@@ -47,6 +47,12 @@ func (s *TestStack) InterfaceAddrs() map[int32][]InterfaceAddr {
return s.InterfaceAddrsMap
}
// AddInterfaceAddr implements Stack.AddInterfaceAddr.
func (s *TestStack) AddInterfaceAddr(idx int32, addr InterfaceAddr) error {
s.InterfaceAddrsMap[idx] = append(s.InterfaceAddrsMap[idx], addr)
return nil
}
// SupportsIPv6 implements Stack.SupportsIPv6.
func (s *TestStack) SupportsIPv6() bool {
return s.SupportsIPv6Flag
+5
View File
@@ -310,6 +310,11 @@ func (s *Stack) InterfaceAddrs() map[int32][]inet.InterfaceAddr {
return addrs
}
// AddInterfaceAddr implements inet.Stack.AddInterfaceAddr.
func (s *Stack) AddInterfaceAddr(idx int32, addr inet.InterfaceAddr) error {
return syserror.EACCES
}
// SupportsIPv6 implements inet.Stack.SupportsIPv6.
func (s *Stack) SupportsIPv6() bool {
return s.supportsIPv6
+13 -1
View File
@@ -1,4 +1,4 @@
load("//tools:defs.bzl", "go_library")
load("//tools:defs.bzl", "go_library", "go_test")
package(licenses = ["notice"])
@@ -33,3 +33,15 @@ go_library(
"//pkg/waiter",
],
)
go_test(
name = "netlink_test",
size = "small",
srcs = [
"message_test.go",
],
deps = [
":netlink",
"//pkg/abi/linux",
],
)
+129
View File
@@ -30,8 +30,16 @@ func alignUp(length int, align uint) int {
return (length + int(align) - 1) &^ (int(align) - 1)
}
// alignPad returns the length of padding required for alignment.
//
// Preconditions: align is a power of two.
func alignPad(length int, align uint) int {
return alignUp(length, align) - length
}
// Message contains a complete serialized netlink message.
type Message struct {
hdr linux.NetlinkMessageHeader
buf []byte
}
@@ -40,10 +48,86 @@ type Message struct {
// The header length will be updated by Finalize.
func NewMessage(hdr linux.NetlinkMessageHeader) *Message {
return &Message{
hdr: hdr,
buf: binary.Marshal(nil, usermem.ByteOrder, hdr),
}
}
// ParseMessage parses the first message seen at buf, returning the rest of the
// buffer. If message is malformed, ok of false is returned. For last message,
// padding check is loose, if there isn't enought padding, whole buf is consumed
// and ok is set to true.
func ParseMessage(buf []byte) (msg *Message, rest []byte, ok bool) {
b := BytesView(buf)
hdrBytes, ok := b.Extract(linux.NetlinkMessageHeaderSize)
if !ok {
return
}
var hdr linux.NetlinkMessageHeader
binary.Unmarshal(hdrBytes, usermem.ByteOrder, &hdr)
// Msg portion.
totalMsgLen := int(hdr.Length)
_, ok = b.Extract(totalMsgLen - linux.NetlinkMessageHeaderSize)
if !ok {
return
}
// Padding.
numPad := alignPad(totalMsgLen, linux.NLMSG_ALIGNTO)
// Linux permits the last message not being aligned, just consume all of it.
// Ref: net/netlink/af_netlink.c:netlink_rcv_skb
if numPad > len(b) {
numPad = len(b)
}
_, ok = b.Extract(numPad)
if !ok {
return
}
return &Message{
hdr: hdr,
buf: buf[:totalMsgLen],
}, []byte(b), true
}
// Header returns the header of this message.
func (m *Message) Header() linux.NetlinkMessageHeader {
return m.hdr
}
// GetData unmarshals the payload message header from this netlink message, and
// returns the attributes portion.
func (m *Message) GetData(msg interface{}) (AttrsView, bool) {
b := BytesView(m.buf)
_, ok := b.Extract(linux.NetlinkMessageHeaderSize)
if !ok {
return nil, false
}
size := int(binary.Size(msg))
msgBytes, ok := b.Extract(size)
if !ok {
return nil, false
}
binary.Unmarshal(msgBytes, usermem.ByteOrder, msg)
numPad := alignPad(linux.NetlinkMessageHeaderSize+size, linux.NLMSG_ALIGNTO)
// Linux permits the last message not being aligned, just consume all of it.
// Ref: net/netlink/af_netlink.c:netlink_rcv_skb
if numPad > len(b) {
numPad = len(b)
}
_, ok = b.Extract(numPad)
if !ok {
return nil, false
}
return AttrsView(b), true
}
// Finalize returns the []byte containing the entire message, with the total
// length set in the message header. The Message must not be modified after
// calling Finalize.
@@ -157,3 +241,48 @@ func (ms *MessageSet) AddMessage(hdr linux.NetlinkMessageHeader) *Message {
ms.Messages = append(ms.Messages, m)
return m
}
// AttrsView is a view into the attributes portion of a netlink message.
type AttrsView []byte
// Empty returns whether there is no attribute left in v.
func (v AttrsView) Empty() bool {
return len(v) == 0
}
// ParseFirst parses first netlink attribute at the beginning of v.
func (v AttrsView) ParseFirst() (hdr linux.NetlinkAttrHeader, value []byte, rest AttrsView, ok bool) {
b := BytesView(v)
hdrBytes, ok := b.Extract(linux.NetlinkAttrHeaderSize)
if !ok {
return
}
binary.Unmarshal(hdrBytes, usermem.ByteOrder, &hdr)
value, ok = b.Extract(int(hdr.Length) - linux.NetlinkAttrHeaderSize)
if !ok {
return
}
_, ok = b.Extract(alignPad(int(hdr.Length), linux.NLA_ALIGNTO))
if !ok {
return
}
return hdr, value, AttrsView(b), ok
}
// BytesView supports extracting data from a byte slice with bounds checking.
type BytesView []byte
// Extract removes the first n bytes from v and returns it. If n is out of
// bounds, it returns false.
func (v *BytesView) Extract(n int) ([]byte, bool) {
if n < 0 || n > len(*v) {
return nil, false
}
extracted := (*v)[:n]
*v = (*v)[n:]
return extracted, true
}
+312
View File
@@ -0,0 +1,312 @@
// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package message_test
import (
"bytes"
"reflect"
"testing"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/socket/netlink"
)
type dummyNetlinkMsg struct {
Foo uint16
}
func TestParseMessage(t *testing.T) {
tests := []struct {
desc string
input []byte
header linux.NetlinkMessageHeader
dataMsg *dummyNetlinkMsg
restLen int
ok bool
}{
{
desc: "valid",
input: []byte{
0x14, 0x00, 0x00, 0x00, // Length
0x01, 0x00, // Type
0x02, 0x00, // Flags
0x03, 0x00, 0x00, 0x00, // Seq
0x04, 0x00, 0x00, 0x00, // PortID
0x30, 0x31, 0x00, 0x00, // Data message with 2 bytes padding
},
header: linux.NetlinkMessageHeader{
Length: 20,
Type: 1,
Flags: 2,
Seq: 3,
PortID: 4,
},
dataMsg: &dummyNetlinkMsg{
Foo: 0x3130,
},
restLen: 0,
ok: true,
},
{
desc: "valid with next message",
input: []byte{
0x14, 0x00, 0x00, 0x00, // Length
0x01, 0x00, // Type
0x02, 0x00, // Flags
0x03, 0x00, 0x00, 0x00, // Seq
0x04, 0x00, 0x00, 0x00, // PortID
0x30, 0x31, 0x00, 0x00, // Data message with 2 bytes padding
0xFF, // Next message (rest)
},
header: linux.NetlinkMessageHeader{
Length: 20,
Type: 1,
Flags: 2,
Seq: 3,
PortID: 4,
},
dataMsg: &dummyNetlinkMsg{
Foo: 0x3130,
},
restLen: 1,
ok: true,
},
{
desc: "valid for last message without padding",
input: []byte{
0x12, 0x00, 0x00, 0x00, // Length
0x01, 0x00, // Type
0x02, 0x00, // Flags
0x03, 0x00, 0x00, 0x00, // Seq
0x04, 0x00, 0x00, 0x00, // PortID
0x30, 0x31, // Data message
},
header: linux.NetlinkMessageHeader{
Length: 18,
Type: 1,
Flags: 2,
Seq: 3,
PortID: 4,
},
dataMsg: &dummyNetlinkMsg{
Foo: 0x3130,
},
restLen: 0,
ok: true,
},
{
desc: "valid for last message not to be aligned",
input: []byte{
0x13, 0x00, 0x00, 0x00, // Length
0x01, 0x00, // Type
0x02, 0x00, // Flags
0x03, 0x00, 0x00, 0x00, // Seq
0x04, 0x00, 0x00, 0x00, // PortID
0x30, 0x31, // Data message
0x00, // Excessive 1 byte permitted at end
},
header: linux.NetlinkMessageHeader{
Length: 19,
Type: 1,
Flags: 2,
Seq: 3,
PortID: 4,
},
dataMsg: &dummyNetlinkMsg{
Foo: 0x3130,
},
restLen: 0,
ok: true,
},
{
desc: "header.Length too short",
input: []byte{
0x04, 0x00, 0x00, 0x00, // Length
0x01, 0x00, // Type
0x02, 0x00, // Flags
0x03, 0x00, 0x00, 0x00, // Seq
0x04, 0x00, 0x00, 0x00, // PortID
0x30, 0x31, 0x00, 0x00, // Data message with 2 bytes padding
},
ok: false,
},
{
desc: "header.Length too long",
input: []byte{
0xFF, 0xFF, 0x00, 0x00, // Length
0x01, 0x00, // Type
0x02, 0x00, // Flags
0x03, 0x00, 0x00, 0x00, // Seq
0x04, 0x00, 0x00, 0x00, // PortID
0x30, 0x31, 0x00, 0x00, // Data message with 2 bytes padding
},
ok: false,
},
{
desc: "header incomplete",
input: []byte{
0x04, 0x00, 0x00, 0x00, // Length
},
ok: false,
},
{
desc: "empty message",
input: []byte{},
ok: false,
},
}
for _, test := range tests {
msg, rest, ok := netlink.ParseMessage(test.input)
if ok != test.ok {
t.Errorf("%v: got ok = %v, want = %v", test.desc, ok, test.ok)
continue
}
if !test.ok {
continue
}
if !reflect.DeepEqual(msg.Header(), test.header) {
t.Errorf("%v: got hdr = %+v, want = %+v", test.desc, msg.Header(), test.header)
}
dataMsg := &dummyNetlinkMsg{}
_, dataOk := msg.GetData(dataMsg)
if !dataOk {
t.Errorf("%v: GetData.ok = %v, want = true", test.desc, dataOk)
} else if !reflect.DeepEqual(dataMsg, test.dataMsg) {
t.Errorf("%v: GetData.msg = %+v, want = %+v", test.desc, dataMsg, test.dataMsg)
}
if got, want := rest, test.input[len(test.input)-test.restLen:]; !bytes.Equal(got, want) {
t.Errorf("%v: got rest = %v, want = %v", test.desc, got, want)
}
}
}
func TestAttrView(t *testing.T) {
tests := []struct {
desc string
input []byte
// Outputs for ParseFirst.
hdr linux.NetlinkAttrHeader
value []byte
restLen int
ok bool
// Outputs for Empty.
isEmpty bool
}{
{
desc: "valid",
input: []byte{
0x06, 0x00, // Length
0x01, 0x00, // Type
0x30, 0x31, 0x00, 0x00, // Data with 2 bytes padding
},
hdr: linux.NetlinkAttrHeader{
Length: 6,
Type: 1,
},
value: []byte{0x30, 0x31},
restLen: 0,
ok: true,
isEmpty: false,
},
{
desc: "at alignment",
input: []byte{
0x08, 0x00, // Length
0x01, 0x00, // Type
0x30, 0x31, 0x32, 0x33, // Data
},
hdr: linux.NetlinkAttrHeader{
Length: 8,
Type: 1,
},
value: []byte{0x30, 0x31, 0x32, 0x33},
restLen: 0,
ok: true,
isEmpty: false,
},
{
desc: "at alignment with rest data",
input: []byte{
0x08, 0x00, // Length
0x01, 0x00, // Type
0x30, 0x31, 0x32, 0x33, // Data
0xFF, 0xFE, // Rest data
},
hdr: linux.NetlinkAttrHeader{
Length: 8,
Type: 1,
},
value: []byte{0x30, 0x31, 0x32, 0x33},
restLen: 2,
ok: true,
isEmpty: false,
},
{
desc: "hdr.Length too long",
input: []byte{
0xFF, 0x00, // Length
0x01, 0x00, // Type
0x30, 0x31, 0x32, 0x33, // Data
},
ok: false,
isEmpty: false,
},
{
desc: "hdr.Length too short",
input: []byte{
0x01, 0x00, // Length
0x01, 0x00, // Type
0x30, 0x31, 0x32, 0x33, // Data
},
ok: false,
isEmpty: false,
},
{
desc: "empty",
input: []byte{},
ok: false,
isEmpty: true,
},
}
for _, test := range tests {
attrs := netlink.AttrsView(test.input)
// Test ParseFirst().
hdr, value, rest, ok := attrs.ParseFirst()
if ok != test.ok {
t.Errorf("%v: got ok = %v, want = %v", test.desc, ok, test.ok)
} else if test.ok {
if !reflect.DeepEqual(hdr, test.hdr) {
t.Errorf("%v: got hdr = %+v, want = %+v", test.desc, hdr, test.hdr)
}
if !bytes.Equal(value, test.value) {
t.Errorf("%v: got value = %v, want = %v", test.desc, value, test.value)
}
if wantRest := test.input[len(test.input)-test.restLen:]; !bytes.Equal(rest, wantRest) {
t.Errorf("%v: got rest = %v, want = %v", test.desc, rest, wantRest)
}
}
// Test Empty().
if got, want := attrs.Empty(), test.isEmpty; got != want {
t.Errorf("%v: got empty = %v, want = %v", test.desc, got, want)
}
}
}
+1 -1
View File
@@ -42,7 +42,7 @@ type Protocol interface {
// If err == nil, any messages added to ms will be sent back to the
// other end of the socket. Setting ms.Multi will cause an NLMSG_DONE
// message to be sent even if ms contains no messages.
ProcessMessage(ctx context.Context, hdr linux.NetlinkMessageHeader, data []byte, ms *MessageSet) *syserr.Error
ProcessMessage(ctx context.Context, msg *Message, ms *MessageSet) *syserr.Error
}
// Provider is a function that creates a new Protocol for a specific netlink
-2
View File
@@ -10,13 +10,11 @@ go_library(
visibility = ["//pkg/sentry:internal"],
deps = [
"//pkg/abi/linux",
"//pkg/binary",
"//pkg/context",
"//pkg/sentry/inet",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/socket/netlink",
"//pkg/syserr",
"//pkg/usermem",
],
)
+177 -71
View File
@@ -17,16 +17,15 @@ package route
import (
"bytes"
"syscall"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/binary"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/socket/netlink"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/usermem"
)
// commandKind describes the operational class of a message type.
@@ -69,13 +68,7 @@ func (p *Protocol) CanSend() bool {
}
// dumpLinks handles RTM_GETLINK dump requests.
func (p *Protocol) dumpLinks(ctx context.Context, hdr linux.NetlinkMessageHeader, data []byte, ms *netlink.MessageSet) *syserr.Error {
// TODO(b/68878065): Only the dump variant of the types below are
// supported.
if hdr.Flags&linux.NLM_F_DUMP != linux.NLM_F_DUMP {
return syserr.ErrNotSupported
}
func (p *Protocol) dumpLinks(ctx context.Context, msg *netlink.Message, ms *netlink.MessageSet) *syserr.Error {
// NLM_F_DUMP + RTM_GETLINK messages are supposed to include an
// ifinfomsg. However, Linux <3.9 only checked for rtgenmsg, and some
// userspace applications (including glibc) still include rtgenmsg.
@@ -99,44 +92,105 @@ func (p *Protocol) dumpLinks(ctx context.Context, hdr linux.NetlinkMessageHeader
return nil
}
for id, i := range stack.Interfaces() {
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: linux.RTM_NEWLINK,
})
m.Put(linux.InterfaceInfoMessage{
Family: linux.AF_UNSPEC,
Type: i.DeviceType,
Index: id,
Flags: i.Flags,
})
m.PutAttrString(linux.IFLA_IFNAME, i.Name)
m.PutAttr(linux.IFLA_MTU, i.MTU)
mac := make([]byte, 6)
brd := mac
if len(i.Addr) > 0 {
mac = i.Addr
brd = bytes.Repeat([]byte{0xff}, len(i.Addr))
}
m.PutAttr(linux.IFLA_ADDRESS, mac)
m.PutAttr(linux.IFLA_BROADCAST, brd)
// TODO(gvisor.dev/issue/578): There are many more attributes.
for idx, i := range stack.Interfaces() {
addNewLinkMessage(ms, idx, i)
}
return nil
}
// dumpAddrs handles RTM_GETADDR dump requests.
func (p *Protocol) dumpAddrs(ctx context.Context, hdr linux.NetlinkMessageHeader, data []byte, ms *netlink.MessageSet) *syserr.Error {
// TODO(b/68878065): Only the dump variant of the types below are
// supported.
if hdr.Flags&linux.NLM_F_DUMP != linux.NLM_F_DUMP {
return syserr.ErrNotSupported
// getLinks handles RTM_GETLINK requests.
func (p *Protocol) getLink(ctx context.Context, msg *netlink.Message, ms *netlink.MessageSet) *syserr.Error {
stack := inet.StackFromContext(ctx)
if stack == nil {
// No network devices.
return nil
}
// Parse message.
var ifi linux.InterfaceInfoMessage
attrs, ok := msg.GetData(&ifi)
if !ok {
return syserr.ErrInvalidArgument
}
// Parse attributes.
var byName []byte
for !attrs.Empty() {
ahdr, value, rest, ok := attrs.ParseFirst()
if !ok {
return syserr.ErrInvalidArgument
}
attrs = rest
switch ahdr.Type {
case linux.IFLA_IFNAME:
if len(value) < 1 {
return syserr.ErrInvalidArgument
}
byName = value[:len(value)-1]
// TODO(gvisor.dev/issue/578): Support IFLA_EXT_MASK.
}
}
found := false
for idx, i := range stack.Interfaces() {
switch {
case ifi.Index > 0:
if idx != ifi.Index {
continue
}
case byName != nil:
if string(byName) != i.Name {
continue
}
default:
// Criteria not specified.
return syserr.ErrInvalidArgument
}
addNewLinkMessage(ms, idx, i)
found = true
break
}
if !found {
return syserr.ErrNoDevice
}
return nil
}
// addNewLinkMessage appends RTM_NEWLINK message for the given interface into
// the message set.
func addNewLinkMessage(ms *netlink.MessageSet, idx int32, i inet.Interface) {
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: linux.RTM_NEWLINK,
})
m.Put(linux.InterfaceInfoMessage{
Family: linux.AF_UNSPEC,
Type: i.DeviceType,
Index: idx,
Flags: i.Flags,
})
m.PutAttrString(linux.IFLA_IFNAME, i.Name)
m.PutAttr(linux.IFLA_MTU, i.MTU)
mac := make([]byte, 6)
brd := mac
if len(i.Addr) > 0 {
mac = i.Addr
brd = bytes.Repeat([]byte{0xff}, len(i.Addr))
}
m.PutAttr(linux.IFLA_ADDRESS, mac)
m.PutAttr(linux.IFLA_BROADCAST, brd)
// TODO(gvisor.dev/issue/578): There are many more attributes.
}
// dumpAddrs handles RTM_GETADDR dump requests.
func (p *Protocol) dumpAddrs(ctx context.Context, msg *netlink.Message, ms *netlink.MessageSet) *syserr.Error {
// RTM_GETADDR dump requests need not contain anything more than the
// netlink header and 1 byte protocol family common to all
// NETLINK_ROUTE requests.
@@ -168,6 +222,7 @@ func (p *Protocol) dumpAddrs(ctx context.Context, hdr linux.NetlinkMessageHeader
Index: uint32(id),
})
m.PutAttr(linux.IFA_LOCAL, []byte(a.Addr))
m.PutAttr(linux.IFA_ADDRESS, []byte(a.Addr))
// TODO(gvisor.dev/issue/578): There are many more attributes.
@@ -252,12 +307,12 @@ func fillRoute(routes []inet.Route, addr []byte) (inet.Route, *syserr.Error) {
}
// parseForDestination parses a message as format of RouteMessage-RtAttr-dst.
func parseForDestination(data []byte) ([]byte, *syserr.Error) {
func parseForDestination(msg *netlink.Message) ([]byte, *syserr.Error) {
var rtMsg linux.RouteMessage
if len(data) < linux.SizeOfRouteMessage {
attrs, ok := msg.GetData(&rtMsg)
if !ok {
return nil, syserr.ErrInvalidArgument
}
binary.Unmarshal(data[:linux.SizeOfRouteMessage], usermem.ByteOrder, &rtMsg)
// iproute2 added the RTM_F_LOOKUP_TABLE flag in version v4.4.0. See
// commit bc234301af12. Note we don't check this flag for backward
// compatibility.
@@ -265,26 +320,15 @@ func parseForDestination(data []byte) ([]byte, *syserr.Error) {
return nil, syserr.ErrNotSupported
}
data = data[linux.SizeOfRouteMessage:]
// TODO(gvisor.dev/issue/1611): Add generic attribute parsing.
var rtAttr linux.RtAttr
if len(data) < linux.SizeOfRtAttr {
return nil, syserr.ErrInvalidArgument
// Expect first attribute is RTA_DST.
if hdr, value, _, ok := attrs.ParseFirst(); ok && hdr.Type == linux.RTA_DST {
return value, nil
}
binary.Unmarshal(data[:linux.SizeOfRtAttr], usermem.ByteOrder, &rtAttr)
if rtAttr.Type != linux.RTA_DST {
return nil, syserr.ErrInvalidArgument
}
if len(data) < int(rtAttr.Len) {
return nil, syserr.ErrInvalidArgument
}
return data[linux.SizeOfRtAttr:rtAttr.Len], nil
return nil, syserr.ErrInvalidArgument
}
// dumpRoutes handles RTM_GETROUTE requests.
func (p *Protocol) dumpRoutes(ctx context.Context, hdr linux.NetlinkMessageHeader, data []byte, ms *netlink.MessageSet) *syserr.Error {
func (p *Protocol) dumpRoutes(ctx context.Context, msg *netlink.Message, 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.
@@ -295,10 +339,11 @@ func (p *Protocol) dumpRoutes(ctx context.Context, hdr linux.NetlinkMessageHeade
return nil
}
hdr := msg.Header()
routeTables := stack.RouteTable()
if hdr.Flags == linux.NLM_F_REQUEST {
dst, err := parseForDestination(data)
dst, err := parseForDestination(msg)
if err != nil {
return err
}
@@ -357,10 +402,55 @@ func (p *Protocol) dumpRoutes(ctx context.Context, hdr linux.NetlinkMessageHeade
return nil
}
// newAddr handles RTM_NEWADDR requests.
func (p *Protocol) newAddr(ctx context.Context, msg *netlink.Message, ms *netlink.MessageSet) *syserr.Error {
stack := inet.StackFromContext(ctx)
if stack == nil {
// No network stack.
return syserr.ErrProtocolNotSupported
}
var ifa linux.InterfaceAddrMessage
attrs, ok := msg.GetData(&ifa)
if !ok {
return syserr.ErrInvalidArgument
}
for !attrs.Empty() {
ahdr, value, rest, ok := attrs.ParseFirst()
if !ok {
return syserr.ErrInvalidArgument
}
attrs = rest
switch ahdr.Type {
case linux.IFA_LOCAL:
err := stack.AddInterfaceAddr(int32(ifa.Index), inet.InterfaceAddr{
Family: ifa.Family,
PrefixLen: ifa.PrefixLen,
Flags: ifa.Flags,
Addr: value,
})
if err == syscall.EEXIST {
flags := msg.Header().Flags
if flags&linux.NLM_F_EXCL != 0 {
return syserr.ErrExists
}
} else if err != nil {
return syserr.ErrInvalidArgument
}
}
}
return nil
}
// ProcessMessage implements netlink.Protocol.ProcessMessage.
func (p *Protocol) ProcessMessage(ctx context.Context, hdr linux.NetlinkMessageHeader, data []byte, ms *netlink.MessageSet) *syserr.Error {
func (p *Protocol) ProcessMessage(ctx context.Context, msg *netlink.Message, ms *netlink.MessageSet) *syserr.Error {
hdr := msg.Header()
// All messages start with a 1 byte protocol family.
if len(data) < 1 {
var family uint8
if _, ok := msg.GetData(&family); !ok {
// Linux ignores messages missing the protocol family. See
// net/core/rtnetlink.c:rtnetlink_rcv_msg.
return nil
@@ -374,16 +464,32 @@ func (p *Protocol) ProcessMessage(ctx context.Context, hdr linux.NetlinkMessageH
}
}
switch hdr.Type {
case linux.RTM_GETLINK:
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
if hdr.Flags&linux.NLM_F_DUMP == linux.NLM_F_DUMP {
// TODO(b/68878065): Only the dump variant of the types below are
// supported.
switch hdr.Type {
case linux.RTM_GETLINK:
return p.dumpLinks(ctx, msg, ms)
case linux.RTM_GETADDR:
return p.dumpAddrs(ctx, msg, ms)
case linux.RTM_GETROUTE:
return p.dumpRoutes(ctx, msg, ms)
default:
return syserr.ErrNotSupported
}
} else if hdr.Flags&linux.NLM_F_REQUEST == linux.NLM_F_REQUEST {
switch hdr.Type {
case linux.RTM_GETLINK:
return p.getLink(ctx, msg, ms)
case linux.RTM_GETROUTE:
return p.dumpRoutes(ctx, msg, ms)
case linux.RTM_NEWADDR:
return p.newAddr(ctx, msg, ms)
default:
return syserr.ErrNotSupported
}
}
return syserr.ErrNotSupported
}
// init registers the NETLINK_ROUTE provider.
+18 -36
View File
@@ -644,47 +644,38 @@ func (s *Socket) sendResponse(ctx context.Context, ms *MessageSet) *syserr.Error
return nil
}
func (s *Socket) dumpErrorMesage(ctx context.Context, hdr linux.NetlinkMessageHeader, ms *MessageSet, err *syserr.Error) *syserr.Error {
func dumpErrorMesage(hdr linux.NetlinkMessageHeader, ms *MessageSet, err *syserr.Error) {
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: linux.NLMSG_ERROR,
})
m.Put(linux.NetlinkErrorMessage{
Error: int32(-err.ToLinux().Number()),
Header: hdr,
})
return nil
}
func dumpAckMesage(hdr linux.NetlinkMessageHeader, ms *MessageSet) {
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: linux.NLMSG_ERROR,
})
m.Put(linux.NetlinkErrorMessage{
Error: 0,
Header: hdr,
})
}
// processMessages handles each message in buf, passing it to the protocol
// handler for final handling.
func (s *Socket) processMessages(ctx context.Context, buf []byte) *syserr.Error {
for len(buf) > 0 {
if len(buf) < linux.NetlinkMessageHeaderSize {
msg, rest, ok := ParseMessage(buf)
if !ok {
// Linux ignores messages that are too short. See
// net/netlink/af_netlink.c:netlink_rcv_skb.
break
}
var hdr linux.NetlinkMessageHeader
binary.Unmarshal(buf[:linux.NetlinkMessageHeaderSize], usermem.ByteOrder, &hdr)
if hdr.Length < linux.NetlinkMessageHeaderSize || uint64(hdr.Length) > uint64(len(buf)) {
// Linux ignores malformed messages. See
// net/netlink/af_netlink.c:netlink_rcv_skb.
break
}
// Data from this message.
data := buf[linux.NetlinkMessageHeaderSize:hdr.Length]
// Advance to the next message.
next := alignUp(int(hdr.Length), linux.NLMSG_ALIGNTO)
if next >= len(buf)-1 {
next = len(buf) - 1
}
buf = buf[next:]
buf = rest
hdr := msg.Header()
// Ignore control messages.
if hdr.Type < linux.NLMSG_MIN_TYPE {
@@ -692,19 +683,10 @@ func (s *Socket) processMessages(ctx context.Context, buf []byte) *syserr.Error
}
ms := NewMessageSet(s.portID, hdr.Seq)
var err *syserr.Error
// TODO(b/68877377): ACKs not supported yet.
if hdr.Flags&linux.NLM_F_ACK == linux.NLM_F_ACK {
err = syserr.ErrNotSupported
} else {
err = s.protocol.ProcessMessage(ctx, hdr, data, ms)
}
if err != nil {
ms = NewMessageSet(s.portID, hdr.Seq)
if err := s.dumpErrorMesage(ctx, hdr, ms, err); err != nil {
return err
}
if err := s.protocol.ProcessMessage(ctx, msg, ms); err != nil {
dumpErrorMesage(hdr, ms, err)
} else if hdr.Flags&linux.NLM_F_ACK == linux.NLM_F_ACK {
dumpAckMesage(hdr, ms)
}
if err := s.sendResponse(ctx, ms); err != nil {
+1 -1
View File
@@ -49,7 +49,7 @@ func (p *Protocol) CanSend() bool {
}
// ProcessMessage implements netlink.Protocol.ProcessMessage.
func (p *Protocol) ProcessMessage(ctx context.Context, hdr linux.NetlinkMessageHeader, data []byte, ms *netlink.MessageSet) *syserr.Error {
func (p *Protocol) ProcessMessage(ctx context.Context, msg *netlink.Message, ms *netlink.MessageSet) *syserr.Error {
// Silently ignore all messages.
return nil
}
+55
View File
@@ -20,6 +20,8 @@ import (
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/sentry/socket/netfilter"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/iptables"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
@@ -88,6 +90,59 @@ func (s *Stack) InterfaceAddrs() map[int32][]inet.InterfaceAddr {
return nicAddrs
}
// AddInterfaceAddr implements inet.Stack.AddInterfaceAddr.
func (s *Stack) AddInterfaceAddr(idx int32, addr inet.InterfaceAddr) error {
var (
protocol tcpip.NetworkProtocolNumber
address tcpip.Address
)
switch addr.Family {
case linux.AF_INET:
if len(addr.Addr) < header.IPv4AddressSize {
return syserror.EINVAL
}
if addr.PrefixLen > header.IPv4AddressSize*8 {
return syserror.EINVAL
}
protocol = ipv4.ProtocolNumber
address = tcpip.Address(addr.Addr[:header.IPv4AddressSize])
case linux.AF_INET6:
if len(addr.Addr) < header.IPv6AddressSize {
return syserror.EINVAL
}
if addr.PrefixLen > header.IPv6AddressSize*8 {
return syserror.EINVAL
}
protocol = ipv6.ProtocolNumber
address = tcpip.Address(addr.Addr[:header.IPv6AddressSize])
default:
return syserror.ENOTSUP
}
protocolAddress := tcpip.ProtocolAddress{
Protocol: protocol,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: address,
PrefixLen: int(addr.PrefixLen),
},
}
// Attach address to interface.
if err := s.Stack.AddProtocolAddressWithOptions(tcpip.NICID(idx), protocolAddress, stack.CanBePrimaryEndpoint); err != nil {
return syserr.TranslateNetstackError(err).ToError()
}
// Add route for local network.
s.Stack.AddRoute(tcpip.Route{
Destination: protocolAddress.AddressWithPrefix.Subnet(),
Gateway: "", // No gateway for local network.
NIC: tcpip.NICID(idx),
})
return nil
}
// TCPReceiveBufferSize implements inet.Stack.TCPReceiveBufferSize.
func (s *Stack) TCPReceiveBufferSize() (inet.TCPBufferSize, error) {
var rs tcp.ReceiveBufferSizeOption
+9
View File
@@ -795,6 +795,8 @@ func (s *Stack) Forwarding() bool {
// SetRouteTable assigns the route table to be used by this stack. It
// specifies which NIC to use for given destination address ranges.
//
// This method takes ownership of the table.
func (s *Stack) SetRouteTable(table []tcpip.Route) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -809,6 +811,13 @@ func (s *Stack) GetRouteTable() []tcpip.Route {
return append([]tcpip.Route(nil), s.routeTable...)
}
// AddRoute appends a route to the route table.
func (s *Stack) AddRoute(route tcpip.Route) {
s.mu.Lock()
defer s.mu.Unlock()
s.routeTable = append(s.routeTable, route)
}
// NewEndpoint creates a new transport layer endpoint of the given protocol.
func (s *Stack) NewEndpoint(transport tcpip.TransportProtocolNumber, network tcpip.NetworkProtocolNumber, waiterQueue *waiter.Queue) (tcpip.Endpoint, *tcpip.Error) {
t, ok := s.transportProtocols[transport]
+2
View File
@@ -2769,9 +2769,11 @@ cc_binary(
deps = [
":socket_netlink_util",
":socket_test_util",
"//test/util:capability_util",
"//test/util:cleanup",
"//test/util:file_descriptor",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/types:optional",
gtest,
"//test/util:test_main",
"//test/util:test_util",
+249 -55
View File
@@ -14,6 +14,7 @@
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <linux/if.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>
@@ -25,8 +26,10 @@
#include "gtest/gtest.h"
#include "absl/strings/str_format.h"
#include "absl/types/optional.h"
#include "test/syscalls/linux/socket_netlink_util.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/capability_util.h"
#include "test/util/cleanup.h"
#include "test/util/file_descriptor.h"
#include "test/util/test_util.h"
@@ -38,6 +41,8 @@ namespace testing {
namespace {
constexpr uint32_t kSeq = 12345;
using ::testing::AnyOf;
using ::testing::Eq;
@@ -113,46 +118,214 @@ void CheckGetLinkResponse(const struct nlmsghdr* hdr, int seq, int port) {
// TODO(mpratt): Check ifinfomsg contents and following attrs.
}
PosixError DumpLinks(
const FileDescriptor& fd, uint32_t seq,
const std::function<void(const struct nlmsghdr* hdr)>& fn) {
struct request {
struct nlmsghdr hdr;
struct ifinfomsg ifm;
};
struct request req = {};
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETLINK;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.hdr.nlmsg_seq = seq;
req.ifm.ifi_family = AF_UNSPEC;
return NetlinkRequestResponse(fd, &req, sizeof(req), fn, false);
}
TEST(NetlinkRouteTest, GetLinkDump) {
FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get()));
// Loopback is common among all tests, check that it's found.
bool loopbackFound = false;
ASSERT_NO_ERRNO(DumpLinks(fd, kSeq, [&](const struct nlmsghdr* hdr) {
CheckGetLinkResponse(hdr, kSeq, port);
if (hdr->nlmsg_type != RTM_NEWLINK) {
return;
}
ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct ifinfomsg)));
const struct ifinfomsg* msg =
reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr));
std::cout << "Found interface idx=" << msg->ifi_index
<< ", type=" << std::hex << msg->ifi_type;
if (msg->ifi_type == ARPHRD_LOOPBACK) {
loopbackFound = true;
EXPECT_NE(msg->ifi_flags & IFF_LOOPBACK, 0);
}
}));
EXPECT_TRUE(loopbackFound);
}
struct Link {
int index;
std::string name;
};
PosixErrorOr<absl::optional<Link>> FindLoopbackLink() {
ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE));
absl::optional<Link> link;
RETURN_IF_ERRNO(DumpLinks(fd, kSeq, [&](const struct nlmsghdr* hdr) {
if (hdr->nlmsg_type != RTM_NEWLINK ||
hdr->nlmsg_len < NLMSG_SPACE(sizeof(struct ifinfomsg))) {
return;
}
const struct ifinfomsg* msg =
reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr));
if (msg->ifi_type == ARPHRD_LOOPBACK) {
const auto* rta = FindRtAttr(hdr, msg, IFLA_IFNAME);
if (rta == nullptr) {
// Ignore links that do not have a name.
return;
}
link = Link();
link->index = msg->ifi_index;
link->name = std::string(reinterpret_cast<const char*>(RTA_DATA(rta)));
}
}));
return link;
}
// CheckLinkMsg checks a netlink message against an expected link.
void CheckLinkMsg(const struct nlmsghdr* hdr, const Link& link) {
ASSERT_THAT(hdr->nlmsg_type, Eq(RTM_NEWLINK));
ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct ifinfomsg)));
const struct ifinfomsg* msg =
reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr));
EXPECT_EQ(msg->ifi_index, link.index);
const struct rtattr* rta = FindRtAttr(hdr, msg, IFLA_IFNAME);
EXPECT_NE(nullptr, rta) << "IFLA_IFNAME not found in message.";
if (rta != nullptr) {
std::string name(reinterpret_cast<const char*>(RTA_DATA(rta)));
EXPECT_EQ(name, link.name);
}
}
TEST(NetlinkRouteTest, GetLinkByIndex) {
absl::optional<Link> loopback_link =
ASSERT_NO_ERRNO_AND_VALUE(FindLoopbackLink());
ASSERT_TRUE(loopback_link.has_value());
FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
struct request {
struct nlmsghdr hdr;
struct ifinfomsg ifm;
};
constexpr uint32_t kSeq = 12345;
struct request req = {};
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETLINK;
req.hdr.nlmsg_flags = NLM_F_REQUEST;
req.hdr.nlmsg_seq = kSeq;
req.ifm.ifi_family = AF_UNSPEC;
req.ifm.ifi_index = loopback_link->index;
bool found = false;
ASSERT_NO_ERRNO(NetlinkRequestResponse(
fd, &req, sizeof(req),
[&](const struct nlmsghdr* hdr) {
CheckLinkMsg(hdr, *loopback_link);
found = true;
},
false));
EXPECT_TRUE(found) << "Netlink response does not contain any links.";
}
TEST(NetlinkRouteTest, GetLinkByName) {
absl::optional<Link> loopback_link =
ASSERT_NO_ERRNO_AND_VALUE(FindLoopbackLink());
ASSERT_TRUE(loopback_link.has_value());
FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
struct request {
struct nlmsghdr hdr;
struct ifinfomsg ifm;
struct rtattr rtattr;
char ifname[IFNAMSIZ];
char pad[NLMSG_ALIGNTO + RTA_ALIGNTO];
};
struct request req = {};
req.hdr.nlmsg_type = RTM_GETLINK;
req.hdr.nlmsg_flags = NLM_F_REQUEST;
req.hdr.nlmsg_seq = kSeq;
req.ifm.ifi_family = AF_UNSPEC;
req.rtattr.rta_type = IFLA_IFNAME;
req.rtattr.rta_len = RTA_LENGTH(loopback_link->name.size() + 1);
strncpy(req.ifname, loopback_link->name.c_str(), sizeof(req.ifname));
req.hdr.nlmsg_len =
NLMSG_LENGTH(sizeof(req.ifm)) + NLMSG_ALIGN(req.rtattr.rta_len);
bool found = false;
ASSERT_NO_ERRNO(NetlinkRequestResponse(
fd, &req, sizeof(req),
[&](const struct nlmsghdr* hdr) {
CheckLinkMsg(hdr, *loopback_link);
found = true;
},
false));
EXPECT_TRUE(found) << "Netlink response does not contain any links.";
}
TEST(NetlinkRouteTest, GetLinkByIndexNotFound) {
FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
struct request {
struct nlmsghdr hdr;
struct ifinfomsg ifm;
};
struct request req = {};
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETLINK;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.hdr.nlmsg_flags = NLM_F_REQUEST;
req.hdr.nlmsg_seq = kSeq;
req.ifm.ifi_family = AF_UNSPEC;
req.ifm.ifi_index = 1234590;
// Loopback is common among all tests, check that it's found.
bool loopbackFound = false;
ASSERT_NO_ERRNO(NetlinkRequestResponse(
fd, &req, sizeof(req),
[&](const struct nlmsghdr* hdr) {
CheckGetLinkResponse(hdr, kSeq, port);
if (hdr->nlmsg_type != RTM_NEWLINK) {
return;
}
ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct ifinfomsg)));
const struct ifinfomsg* msg =
reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr));
std::cout << "Found interface idx=" << msg->ifi_index
<< ", type=" << std::hex << msg->ifi_type;
if (msg->ifi_type == ARPHRD_LOOPBACK) {
loopbackFound = true;
EXPECT_NE(msg->ifi_flags & IFF_LOOPBACK, 0);
}
},
false));
EXPECT_TRUE(loopbackFound);
EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)),
PosixErrorIs(ENODEV, ::testing::_));
}
TEST(NetlinkRouteTest, GetLinkByNameNotFound) {
const std::string name = "nodevice?!";
FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
struct request {
struct nlmsghdr hdr;
struct ifinfomsg ifm;
struct rtattr rtattr;
char ifname[IFNAMSIZ];
char pad[NLMSG_ALIGNTO + RTA_ALIGNTO];
};
struct request req = {};
req.hdr.nlmsg_type = RTM_GETLINK;
req.hdr.nlmsg_flags = NLM_F_REQUEST;
req.hdr.nlmsg_seq = kSeq;
req.ifm.ifi_family = AF_UNSPEC;
req.rtattr.rta_type = IFLA_IFNAME;
req.rtattr.rta_len = RTA_LENGTH(name.size() + 1);
strncpy(req.ifname, name.c_str(), sizeof(req.ifname));
req.hdr.nlmsg_len =
NLMSG_LENGTH(sizeof(req.ifm)) + NLMSG_ALIGN(req.rtattr.rta_len);
EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)),
PosixErrorIs(ENODEV, ::testing::_));
}
TEST(NetlinkRouteTest, MsgHdrMsgUnsuppType) {
@@ -164,8 +337,6 @@ TEST(NetlinkRouteTest, MsgHdrMsgUnsuppType) {
struct ifinfomsg ifm;
};
constexpr uint32_t kSeq = 12345;
struct request req = {};
req.hdr.nlmsg_len = sizeof(req);
// If type & 0x3 is equal to 0x2, this means a get request
@@ -175,18 +346,8 @@ TEST(NetlinkRouteTest, MsgHdrMsgUnsuppType) {
req.hdr.nlmsg_seq = kSeq;
req.ifm.ifi_family = AF_UNSPEC;
ASSERT_NO_ERRNO(NetlinkRequestResponse(
fd, &req, sizeof(req),
[&](const struct nlmsghdr* hdr) {
EXPECT_THAT(hdr->nlmsg_type, Eq(NLMSG_ERROR));
EXPECT_EQ(hdr->nlmsg_seq, kSeq);
EXPECT_GE(hdr->nlmsg_len, sizeof(*hdr) + sizeof(struct nlmsgerr));
const struct nlmsgerr* msg =
reinterpret_cast<const struct nlmsgerr*>(NLMSG_DATA(hdr));
EXPECT_EQ(msg->error, -EOPNOTSUPP);
},
true));
EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)),
PosixErrorIs(EOPNOTSUPP, ::testing::_));
}
TEST(NetlinkRouteTest, MsgHdrMsgTrunc) {
@@ -198,8 +359,6 @@ TEST(NetlinkRouteTest, MsgHdrMsgTrunc) {
struct ifinfomsg ifm;
};
constexpr uint32_t kSeq = 12345;
struct request req = {};
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETLINK;
@@ -238,8 +397,6 @@ TEST(NetlinkRouteTest, MsgTruncMsgHdrMsgTrunc) {
struct ifinfomsg ifm;
};
constexpr uint32_t kSeq = 12345;
struct request req = {};
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETLINK;
@@ -282,8 +439,6 @@ TEST(NetlinkRouteTest, ControlMessageIgnored) {
struct ifinfomsg ifm;
};
constexpr uint32_t kSeq = 12345;
struct request req = {};
// This control message is ignored. We still receive a response for the
@@ -317,8 +472,6 @@ TEST(NetlinkRouteTest, GetAddrDump) {
struct rtgenmsg rgm;
};
constexpr uint32_t kSeq = 12345;
struct request req;
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETADDR;
@@ -367,6 +520,57 @@ TEST(NetlinkRouteTest, LookupAll) {
ASSERT_GT(count, 0);
}
TEST(NetlinkRouteTest, AddAddr) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
absl::optional<Link> loopback_link =
ASSERT_NO_ERRNO_AND_VALUE(FindLoopbackLink());
ASSERT_TRUE(loopback_link.has_value());
FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
struct request {
struct nlmsghdr hdr;
struct ifaddrmsg ifa;
struct rtattr rtattr;
struct in_addr addr;
char pad[NLMSG_ALIGNTO + RTA_ALIGNTO];
};
struct request req = {};
req.hdr.nlmsg_type = RTM_NEWADDR;
req.hdr.nlmsg_seq = kSeq;
req.ifa.ifa_family = AF_INET;
req.ifa.ifa_prefixlen = 24;
req.ifa.ifa_flags = 0;
req.ifa.ifa_scope = 0;
req.ifa.ifa_index = loopback_link->index;
req.rtattr.rta_type = IFA_LOCAL;
req.rtattr.rta_len = RTA_LENGTH(sizeof(req.addr));
inet_pton(AF_INET, "10.0.0.1", &req.addr);
req.hdr.nlmsg_len =
NLMSG_LENGTH(sizeof(req.ifa)) + NLMSG_ALIGN(req.rtattr.rta_len);
// Create should succeed, as no such address in kernel.
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
EXPECT_NO_ERRNO(
NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len));
// Replace an existing address should succeed.
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_ACK;
req.hdr.nlmsg_seq++;
EXPECT_NO_ERRNO(
NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len));
// Create exclusive should fail, as we created the address above.
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK;
req.hdr.nlmsg_seq++;
EXPECT_THAT(
NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len),
PosixErrorIs(EEXIST, ::testing::_));
}
// GetRouteDump tests a RTM_GETROUTE + NLM_F_DUMP request.
TEST(NetlinkRouteTest, GetRouteDump) {
FileDescriptor fd =
@@ -378,8 +582,6 @@ TEST(NetlinkRouteTest, GetRouteDump) {
struct rtmsg rtm;
};
constexpr uint32_t kSeq = 12345;
struct request req = {};
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETROUTE;
@@ -538,8 +740,6 @@ TEST(NetlinkRouteTest, RecvmsgTrunc) {
struct rtgenmsg rgm;
};
constexpr uint32_t kSeq = 12345;
struct request req;
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETADDR;
@@ -615,8 +815,6 @@ TEST(NetlinkRouteTest, RecvmsgTruncPeek) {
struct rtgenmsg rgm;
};
constexpr uint32_t kSeq = 12345;
struct request req;
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETADDR;
@@ -695,8 +893,6 @@ TEST(NetlinkRouteTest, NoPasscredNoCreds) {
struct rtgenmsg rgm;
};
constexpr uint32_t kSeq = 12345;
struct request req;
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETADDR;
@@ -743,8 +939,6 @@ TEST(NetlinkRouteTest, PasscredCreds) {
struct rtgenmsg rgm;
};
constexpr uint32_t kSeq = 12345;
struct request req;
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETADDR;
+41 -4
View File
@@ -16,6 +16,7 @@
#include <linux/if_arp.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>
#include <vector>
@@ -71,9 +72,10 @@ PosixError NetlinkRequestResponse(
iov.iov_base = buf.data();
iov.iov_len = buf.size();
// Response is a series of NLM_F_MULTI messages, ending with a NLMSG_DONE
// message.
// If NLM_F_MULTI is set, response is a series of messages that ends with a
// NLMSG_DONE message.
int type = -1;
int flags = 0;
do {
int len;
RETURN_ERROR_IF_SYSCALL_FAIL(len = RetryEINTR(recvmsg)(fd.get(), &msg, 0));
@@ -89,6 +91,7 @@ PosixError NetlinkRequestResponse(
for (struct nlmsghdr* hdr = reinterpret_cast<struct nlmsghdr*>(buf.data());
NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) {
fn(hdr);
flags = hdr->nlmsg_flags;
type = hdr->nlmsg_type;
// Done should include an integer payload for dump_done_errno.
// See net/netlink/af_netlink.c:netlink_dump
@@ -98,11 +101,11 @@ PosixError NetlinkRequestResponse(
EXPECT_GE(hdr->nlmsg_len, NLMSG_LENGTH(sizeof(int)));
}
}
} while (type != NLMSG_DONE && type != NLMSG_ERROR);
} while ((flags & NLM_F_MULTI) && type != NLMSG_DONE && type != NLMSG_ERROR);
if (expect_nlmsgerr) {
EXPECT_EQ(type, NLMSG_ERROR);
} else {
} else if (flags & NLM_F_MULTI) {
EXPECT_EQ(type, NLMSG_DONE);
}
return NoError();
@@ -146,5 +149,39 @@ PosixError NetlinkRequestResponseSingle(
return NoError();
}
PosixError NetlinkRequestAckOrError(const FileDescriptor& fd, uint32_t seq,
void* request, size_t len) {
// Dummy negative number for no error message received.
// We won't get a negative error number so there will be no confusion.
int err = -42;
RETURN_IF_ERRNO(NetlinkRequestResponse(
fd, request, len,
[&](const struct nlmsghdr* hdr) {
EXPECT_EQ(NLMSG_ERROR, hdr->nlmsg_type);
EXPECT_EQ(hdr->nlmsg_seq, seq);
EXPECT_GE(hdr->nlmsg_len, sizeof(*hdr) + sizeof(struct nlmsgerr));
const struct nlmsgerr* msg =
reinterpret_cast<const struct nlmsgerr*>(NLMSG_DATA(hdr));
err = -msg->error;
},
true));
return PosixError(err);
}
const struct rtattr* FindRtAttr(const struct nlmsghdr* hdr,
const struct ifinfomsg* msg, int16_t attr) {
const int ifi_space = NLMSG_SPACE(sizeof(*msg));
int attrlen = hdr->nlmsg_len - ifi_space;
const struct rtattr* rta = reinterpret_cast<const struct rtattr*>(
reinterpret_cast<const uint8_t*>(hdr) + NLMSG_ALIGN(ifi_space));
for (; RTA_OK(rta, attrlen); rta = RTA_NEXT(rta, attrlen)) {
if (rta->rta_type == attr) {
return rta;
}
}
return nullptr;
}
} // namespace testing
} // namespace gvisor
@@ -19,6 +19,7 @@
// socket.h has to be included before if_arp.h.
#include <linux/if_arp.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include "test/util/file_descriptor.h"
#include "test/util/posix_error.h"
@@ -47,6 +48,14 @@ PosixError NetlinkRequestResponseSingle(
const FileDescriptor& fd, void* request, size_t len,
const std::function<void(const struct nlmsghdr* hdr)>& fn);
// Send the passed request then expect and return an ack or error.
PosixError NetlinkRequestAckOrError(const FileDescriptor& fd, uint32_t seq,
void* request, size_t len);
// Find rtnetlink attribute in message.
const struct rtattr* FindRtAttr(const struct nlmsghdr* hdr,
const struct ifinfomsg* msg, int16_t attr);
} // namespace testing
} // namespace gvisor