mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Support RTM_NEWADDR and RTM_GETLINK in (rt)netlink.
PiperOrigin-RevId: 293271055
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user