Add NETLINK_KOBJECT_UEVENT socket support
NETLINK_KOBJECT_UEVENT sockets send udev-style messages for device events. gVisor doesn't have any device events, so our sockets don't need to do anything once created. systemd's device manager needs to be able to create one of these sockets. It also wants to install a BPF filter on the socket. Since we'll never send any messages, the filter would never be invoked, thus we just fake it out. Fixes #1117 Updates #1119 PiperOrigin-RevId: 278405893
This commit is contained in:
parent
3b4f5445d0
commit
b23b36e701
|
@ -30,6 +30,13 @@ type Protocol interface {
|
|||
// Protocol returns the Linux netlink protocol value.
|
||||
Protocol() int
|
||||
|
||||
// CanSend returns true if this protocol may ever send messages.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1119): This is a workaround to allow
|
||||
// advertising support for otherwise unimplemented features on sockets
|
||||
// that will never send messages, thus making those features no-ops.
|
||||
CanSend() bool
|
||||
|
||||
// ProcessMessage processes a single message from userspace.
|
||||
//
|
||||
// If err == nil, any messages added to ms will be sent back to the
|
||||
|
|
|
@ -61,6 +61,11 @@ func (p *Protocol) Protocol() int {
|
|||
return linux.NETLINK_ROUTE
|
||||
}
|
||||
|
||||
// CanSend implements netlink.Protocol.CanSend.
|
||||
func (p *Protocol) CanSend() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// dumpLinks handles RTM_GETLINK + NLM_F_DUMP requests.
|
||||
func (p *Protocol) dumpLinks(ctx context.Context, hdr linux.NetlinkMessageHeader, data []byte, ms *netlink.MessageSet) *syserr.Error {
|
||||
// NLM_F_DUMP + RTM_GETLINK messages are supposed to include an
|
||||
|
|
|
@ -54,6 +54,8 @@ const (
|
|||
maxSendBufferSize = 4 << 20 // 4MB
|
||||
)
|
||||
|
||||
var errNoFilter = syserr.New("no filter attached", linux.ENOENT)
|
||||
|
||||
// netlinkSocketDevice is the netlink socket virtual device.
|
||||
var netlinkSocketDevice = device.NewAnonDevice()
|
||||
|
||||
|
@ -108,6 +110,12 @@ type Socket struct {
|
|||
|
||||
// passcred indicates if this socket wants SCM credentials.
|
||||
passcred bool
|
||||
|
||||
// filter indicates that this socket has a BPF filter "installed".
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1119): We don't actually support filtering,
|
||||
// this is just bookkeeping for tracking add/remove.
|
||||
filter bool
|
||||
}
|
||||
|
||||
var _ socket.Socket = (*Socket)(nil)
|
||||
|
@ -400,6 +408,40 @@ func (s *Socket) SetSockOpt(t *kernel.Task, level int, name int, opt []byte) *sy
|
|||
s.mu.Unlock()
|
||||
return nil
|
||||
|
||||
case linux.SO_ATTACH_FILTER:
|
||||
// TODO(gvisor.dev/issue/1119): We don't actually
|
||||
// support filtering. If this socket can't ever send
|
||||
// messages, then there is nothing to filter and we can
|
||||
// advertise support. Otherwise, be conservative and
|
||||
// return an error.
|
||||
if s.protocol.CanSend() {
|
||||
socket.SetSockOptEmitUnimplementedEvent(t, name)
|
||||
return syserr.ErrProtocolNotAvailable
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.filter = true
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
|
||||
case linux.SO_DETACH_FILTER:
|
||||
// TODO(gvisor.dev/issue/1119): See above.
|
||||
if s.protocol.CanSend() {
|
||||
socket.SetSockOptEmitUnimplementedEvent(t, name)
|
||||
return syserr.ErrProtocolNotAvailable
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
filter := s.filter
|
||||
s.filter = false
|
||||
s.mu.Unlock()
|
||||
|
||||
if !filter {
|
||||
return errNoFilter
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
default:
|
||||
socket.SetSockOptEmitUnimplementedEvent(t, name)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
load("//tools/go_stateify:defs.bzl", "go_library")
|
||||
|
||||
package(licenses = ["notice"])
|
||||
|
||||
go_library(
|
||||
name = "uevent",
|
||||
srcs = ["protocol.go"],
|
||||
importpath = "gvisor.dev/gvisor/pkg/sentry/socket/netlink/uevent",
|
||||
visibility = ["//pkg/sentry:internal"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/sentry/context",
|
||||
"//pkg/sentry/kernel",
|
||||
"//pkg/sentry/socket/netlink",
|
||||
"//pkg/syserr",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2019 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 uevent provides a NETLINK_KOBJECT_UEVENT socket protocol.
|
||||
//
|
||||
// NETLINK_KOBJECT_UEVENT sockets send udev-style device events. gVisor does
|
||||
// not support any device events, so these sockets never send any messages.
|
||||
package uevent
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/socket/netlink"
|
||||
"gvisor.dev/gvisor/pkg/syserr"
|
||||
)
|
||||
|
||||
// Protocol implements netlink.Protocol.
|
||||
//
|
||||
// +stateify savable
|
||||
type Protocol struct{}
|
||||
|
||||
var _ netlink.Protocol = (*Protocol)(nil)
|
||||
|
||||
// NewProtocol creates a NETLINK_KOBJECT_UEVENT netlink.Protocol.
|
||||
func NewProtocol(t *kernel.Task) (netlink.Protocol, *syserr.Error) {
|
||||
return &Protocol{}, nil
|
||||
}
|
||||
|
||||
// Protocol implements netlink.Protocol.Protocol.
|
||||
func (p *Protocol) Protocol() int {
|
||||
return linux.NETLINK_KOBJECT_UEVENT
|
||||
}
|
||||
|
||||
// CanSend implements netlink.Protocol.CanSend.
|
||||
func (p *Protocol) CanSend() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ProcessMessage implements netlink.Protocol.ProcessMessage.
|
||||
func (p *Protocol) ProcessMessage(ctx context.Context, hdr linux.NetlinkMessageHeader, data []byte, ms *netlink.MessageSet) *syserr.Error {
|
||||
// Silently ignore all messages.
|
||||
return nil
|
||||
}
|
||||
|
||||
// init registers the NETLINK_KOBJECT_UEVENT provider.
|
||||
func init() {
|
||||
netlink.RegisterProvider(linux.NETLINK_KOBJECT_UEVENT, NewProtocol)
|
||||
}
|
|
@ -60,6 +60,7 @@ go_library(
|
|||
"//pkg/sentry/socket/hostinet",
|
||||
"//pkg/sentry/socket/netlink",
|
||||
"//pkg/sentry/socket/netlink/route",
|
||||
"//pkg/sentry/socket/netlink/uevent",
|
||||
"//pkg/sentry/socket/netstack",
|
||||
"//pkg/sentry/socket/unix",
|
||||
"//pkg/sentry/state",
|
||||
|
|
|
@ -65,6 +65,7 @@ import (
|
|||
"gvisor.dev/gvisor/pkg/sentry/socket/hostinet"
|
||||
_ "gvisor.dev/gvisor/pkg/sentry/socket/netlink"
|
||||
_ "gvisor.dev/gvisor/pkg/sentry/socket/netlink/route"
|
||||
_ "gvisor.dev/gvisor/pkg/sentry/socket/netlink/uevent"
|
||||
"gvisor.dev/gvisor/pkg/sentry/socket/netstack"
|
||||
_ "gvisor.dev/gvisor/pkg/sentry/socket/unix"
|
||||
)
|
||||
|
|
|
@ -511,8 +511,12 @@ syscall_test(test = "//test/syscalls/linux:socket_ip_unbound_test")
|
|||
|
||||
syscall_test(test = "//test/syscalls/linux:socket_netdevice_test")
|
||||
|
||||
syscall_test(test = "//test/syscalls/linux:socket_netlink_test")
|
||||
|
||||
syscall_test(test = "//test/syscalls/linux:socket_netlink_route_test")
|
||||
|
||||
syscall_test(test = "//test/syscalls/linux:socket_netlink_uevent_test")
|
||||
|
||||
syscall_test(test = "//test/syscalls/linux:socket_blocking_local_test")
|
||||
|
||||
syscall_test(test = "//test/syscalls/linux:socket_blocking_ip_test")
|
||||
|
|
|
@ -2675,6 +2675,20 @@ cc_binary(
|
|||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "socket_netlink_test",
|
||||
testonly = 1,
|
||||
srcs = ["socket_netlink.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":socket_test_util",
|
||||
"//test/util:file_descriptor",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "socket_netlink_route_test",
|
||||
testonly = 1,
|
||||
|
@ -2692,6 +2706,21 @@ cc_binary(
|
|||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "socket_netlink_uevent_test",
|
||||
testonly = 1,
|
||||
srcs = ["socket_netlink_uevent.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":socket_netlink_util",
|
||||
":socket_test_util",
|
||||
"//test/util:file_descriptor",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
)
|
||||
|
||||
# These socket tests are in a library because the test cases are shared
|
||||
# across several test build targets.
|
||||
cc_library(
|
||||
|
|
|
@ -68,7 +68,8 @@ TEST(NetdeviceTest, Netmask) {
|
|||
|
||||
// Use a netlink socket to get the netmask, which we'll then compare to the
|
||||
// netmask obtained via ioctl.
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
|
||||
uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get()));
|
||||
|
||||
struct request {
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
// Copyright 2018 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.
|
||||
|
||||
#include <linux/netlink.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/syscalls/linux/socket_test_util.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
// Tests for all netlink socket protocols.
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
// NetlinkTest parameter is the protocol to test.
|
||||
using NetlinkTest = ::testing::TestWithParam<int>;
|
||||
|
||||
// Netlink sockets must be SOCK_DGRAM or SOCK_RAW.
|
||||
TEST_P(NetlinkTest, Types) {
|
||||
const int protocol = GetParam();
|
||||
|
||||
EXPECT_THAT(socket(AF_NETLINK, SOCK_STREAM, protocol),
|
||||
SyscallFailsWithErrno(ESOCKTNOSUPPORT));
|
||||
EXPECT_THAT(socket(AF_NETLINK, SOCK_SEQPACKET, protocol),
|
||||
SyscallFailsWithErrno(ESOCKTNOSUPPORT));
|
||||
EXPECT_THAT(socket(AF_NETLINK, SOCK_RDM, protocol),
|
||||
SyscallFailsWithErrno(ESOCKTNOSUPPORT));
|
||||
EXPECT_THAT(socket(AF_NETLINK, SOCK_DCCP, protocol),
|
||||
SyscallFailsWithErrno(ESOCKTNOSUPPORT));
|
||||
EXPECT_THAT(socket(AF_NETLINK, SOCK_PACKET, protocol),
|
||||
SyscallFailsWithErrno(ESOCKTNOSUPPORT));
|
||||
|
||||
int fd;
|
||||
EXPECT_THAT(fd = socket(AF_NETLINK, SOCK_DGRAM, protocol), SyscallSucceeds());
|
||||
EXPECT_THAT(close(fd), SyscallSucceeds());
|
||||
|
||||
EXPECT_THAT(fd = socket(AF_NETLINK, SOCK_RAW, protocol), SyscallSucceeds());
|
||||
EXPECT_THAT(close(fd), SyscallSucceeds());
|
||||
}
|
||||
|
||||
TEST_P(NetlinkTest, AutomaticPort) {
|
||||
const int protocol = GetParam();
|
||||
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, protocol));
|
||||
|
||||
struct sockaddr_nl addr = {};
|
||||
addr.nl_family = AF_NETLINK;
|
||||
|
||||
EXPECT_THAT(
|
||||
bind(fd.get(), reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)),
|
||||
SyscallSucceeds());
|
||||
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
EXPECT_THAT(getsockname(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
|
||||
&addrlen),
|
||||
SyscallSucceeds());
|
||||
EXPECT_EQ(addrlen, sizeof(addr));
|
||||
// This is the only netlink socket in the process, so it should get the PID as
|
||||
// the port id.
|
||||
//
|
||||
// N.B. Another process could theoretically have explicitly reserved our pid
|
||||
// as a port ID, but that is very unlikely.
|
||||
EXPECT_EQ(addr.nl_pid, getpid());
|
||||
}
|
||||
|
||||
// Calling connect automatically binds to an automatic port.
|
||||
TEST_P(NetlinkTest, ConnectBinds) {
|
||||
const int protocol = GetParam();
|
||||
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, protocol));
|
||||
|
||||
struct sockaddr_nl addr = {};
|
||||
addr.nl_family = AF_NETLINK;
|
||||
|
||||
EXPECT_THAT(connect(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
|
||||
sizeof(addr)),
|
||||
SyscallSucceeds());
|
||||
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
EXPECT_THAT(getsockname(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
|
||||
&addrlen),
|
||||
SyscallSucceeds());
|
||||
EXPECT_EQ(addrlen, sizeof(addr));
|
||||
|
||||
// Each test is running in a pid namespace, so another process can explicitly
|
||||
// reserve our pid as a port ID. In this case, a negative portid value will be
|
||||
// set.
|
||||
if (static_cast<pid_t>(addr.nl_pid) > 0) {
|
||||
EXPECT_EQ(addr.nl_pid, getpid());
|
||||
}
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.nl_family = AF_NETLINK;
|
||||
|
||||
// Connecting again is allowed, but keeps the same port.
|
||||
EXPECT_THAT(connect(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
|
||||
sizeof(addr)),
|
||||
SyscallSucceeds());
|
||||
|
||||
addrlen = sizeof(addr);
|
||||
EXPECT_THAT(getsockname(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
|
||||
&addrlen),
|
||||
SyscallSucceeds());
|
||||
EXPECT_EQ(addrlen, sizeof(addr));
|
||||
EXPECT_EQ(addr.nl_pid, getpid());
|
||||
}
|
||||
|
||||
TEST_P(NetlinkTest, GetPeerName) {
|
||||
const int protocol = GetParam();
|
||||
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, protocol));
|
||||
|
||||
struct sockaddr_nl addr = {};
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
|
||||
EXPECT_THAT(getpeername(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
|
||||
&addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
EXPECT_EQ(addrlen, sizeof(addr));
|
||||
EXPECT_EQ(addr.nl_family, AF_NETLINK);
|
||||
// Peer is the kernel if we didn't connect elsewhere.
|
||||
EXPECT_EQ(addr.nl_pid, 0);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ProtocolTest, NetlinkTest,
|
||||
::testing::Values(NETLINK_ROUTE,
|
||||
NETLINK_KOBJECT_UEVENT));
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
|
@ -41,112 +41,7 @@ namespace {
|
|||
using ::testing::AnyOf;
|
||||
using ::testing::Eq;
|
||||
|
||||
// Netlink sockets must be SOCK_DGRAM or SOCK_RAW.
|
||||
TEST(NetlinkRouteTest, Types) {
|
||||
EXPECT_THAT(socket(AF_NETLINK, SOCK_STREAM, NETLINK_ROUTE),
|
||||
SyscallFailsWithErrno(ESOCKTNOSUPPORT));
|
||||
EXPECT_THAT(socket(AF_NETLINK, SOCK_SEQPACKET, NETLINK_ROUTE),
|
||||
SyscallFailsWithErrno(ESOCKTNOSUPPORT));
|
||||
EXPECT_THAT(socket(AF_NETLINK, SOCK_RDM, NETLINK_ROUTE),
|
||||
SyscallFailsWithErrno(ESOCKTNOSUPPORT));
|
||||
EXPECT_THAT(socket(AF_NETLINK, SOCK_DCCP, NETLINK_ROUTE),
|
||||
SyscallFailsWithErrno(ESOCKTNOSUPPORT));
|
||||
EXPECT_THAT(socket(AF_NETLINK, SOCK_PACKET, NETLINK_ROUTE),
|
||||
SyscallFailsWithErrno(ESOCKTNOSUPPORT));
|
||||
|
||||
int fd;
|
||||
EXPECT_THAT(fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE),
|
||||
SyscallSucceeds());
|
||||
EXPECT_THAT(close(fd), SyscallSucceeds());
|
||||
|
||||
EXPECT_THAT(fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE),
|
||||
SyscallSucceeds());
|
||||
EXPECT_THAT(close(fd), SyscallSucceeds());
|
||||
}
|
||||
|
||||
TEST(NetlinkRouteTest, AutomaticPort) {
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE));
|
||||
|
||||
struct sockaddr_nl addr = {};
|
||||
addr.nl_family = AF_NETLINK;
|
||||
|
||||
EXPECT_THAT(
|
||||
bind(fd.get(), reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)),
|
||||
SyscallSucceeds());
|
||||
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
EXPECT_THAT(getsockname(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
|
||||
&addrlen),
|
||||
SyscallSucceeds());
|
||||
EXPECT_EQ(addrlen, sizeof(addr));
|
||||
// This is the only netlink socket in the process, so it should get the PID as
|
||||
// the port id.
|
||||
//
|
||||
// N.B. Another process could theoretically have explicitly reserved our pid
|
||||
// as a port ID, but that is very unlikely.
|
||||
EXPECT_EQ(addr.nl_pid, getpid());
|
||||
}
|
||||
|
||||
// Calling connect automatically binds to an automatic port.
|
||||
TEST(NetlinkRouteTest, ConnectBinds) {
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE));
|
||||
|
||||
struct sockaddr_nl addr = {};
|
||||
addr.nl_family = AF_NETLINK;
|
||||
|
||||
EXPECT_THAT(connect(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
|
||||
sizeof(addr)),
|
||||
SyscallSucceeds());
|
||||
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
EXPECT_THAT(getsockname(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
|
||||
&addrlen),
|
||||
SyscallSucceeds());
|
||||
EXPECT_EQ(addrlen, sizeof(addr));
|
||||
|
||||
// Each test is running in a pid namespace, so another process can explicitly
|
||||
// reserve our pid as a port ID. In this case, a negative portid value will be
|
||||
// set.
|
||||
if (static_cast<pid_t>(addr.nl_pid) > 0) {
|
||||
EXPECT_EQ(addr.nl_pid, getpid());
|
||||
}
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.nl_family = AF_NETLINK;
|
||||
|
||||
// Connecting again is allowed, but keeps the same port.
|
||||
EXPECT_THAT(connect(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
|
||||
sizeof(addr)),
|
||||
SyscallSucceeds());
|
||||
|
||||
addrlen = sizeof(addr);
|
||||
EXPECT_THAT(getsockname(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
|
||||
&addrlen),
|
||||
SyscallSucceeds());
|
||||
EXPECT_EQ(addrlen, sizeof(addr));
|
||||
EXPECT_EQ(addr.nl_pid, getpid());
|
||||
}
|
||||
|
||||
TEST(NetlinkRouteTest, GetPeerName) {
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE));
|
||||
|
||||
struct sockaddr_nl addr = {};
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
|
||||
EXPECT_THAT(getpeername(fd.get(), reinterpret_cast<struct sockaddr*>(&addr),
|
||||
&addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
EXPECT_EQ(addrlen, sizeof(addr));
|
||||
EXPECT_EQ(addr.nl_family, AF_NETLINK);
|
||||
// Peer is the kernel if we didn't connect elsewhere.
|
||||
EXPECT_EQ(addr.nl_pid, 0);
|
||||
}
|
||||
|
||||
// Parameters for GetSockOpt test. They are:
|
||||
// Parameters for SockOptTest. They are:
|
||||
// 0: Socket option to query.
|
||||
// 1: A predicate to run on the returned sockopt value. Should return true if
|
||||
// the value is considered ok.
|
||||
|
@ -219,7 +114,8 @@ void CheckGetLinkResponse(const struct nlmsghdr* hdr, int seq, int port) {
|
|||
}
|
||||
|
||||
TEST(NetlinkRouteTest, GetLinkDump) {
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
|
||||
uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get()));
|
||||
|
||||
struct request {
|
||||
|
@ -260,7 +156,8 @@ TEST(NetlinkRouteTest, GetLinkDump) {
|
|||
}
|
||||
|
||||
TEST(NetlinkRouteTest, MsgHdrMsgUnsuppType) {
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
|
||||
|
||||
struct request {
|
||||
struct nlmsghdr hdr;
|
||||
|
@ -293,7 +190,8 @@ TEST(NetlinkRouteTest, MsgHdrMsgUnsuppType) {
|
|||
}
|
||||
|
||||
TEST(NetlinkRouteTest, MsgHdrMsgTrunc) {
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
|
||||
|
||||
struct request {
|
||||
struct nlmsghdr hdr;
|
||||
|
@ -332,7 +230,8 @@ TEST(NetlinkRouteTest, MsgHdrMsgTrunc) {
|
|||
}
|
||||
|
||||
TEST(NetlinkRouteTest, MsgTruncMsgHdrMsgTrunc) {
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
|
||||
|
||||
struct request {
|
||||
struct nlmsghdr hdr;
|
||||
|
@ -373,7 +272,8 @@ TEST(NetlinkRouteTest, MsgTruncMsgHdrMsgTrunc) {
|
|||
}
|
||||
|
||||
TEST(NetlinkRouteTest, ControlMessageIgnored) {
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
|
||||
uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get()));
|
||||
|
||||
struct request {
|
||||
|
@ -408,7 +308,8 @@ TEST(NetlinkRouteTest, ControlMessageIgnored) {
|
|||
}
|
||||
|
||||
TEST(NetlinkRouteTest, GetAddrDump) {
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
|
||||
uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get()));
|
||||
|
||||
struct request {
|
||||
|
@ -468,7 +369,8 @@ TEST(NetlinkRouteTest, LookupAll) {
|
|||
|
||||
// GetRouteDump tests a RTM_GETROUTE + NLM_F_DUMP request.
|
||||
TEST(NetlinkRouteTest, GetRouteDump) {
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
|
||||
uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get()));
|
||||
|
||||
struct request {
|
||||
|
@ -544,7 +446,8 @@ TEST(NetlinkRouteTest, GetRouteDump) {
|
|||
// buffer. MSG_TRUNC with a zero length buffer should consume subsequent
|
||||
// messages off the socket.
|
||||
TEST(NetlinkRouteTest, RecvmsgTrunc) {
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
|
||||
|
||||
struct request {
|
||||
struct nlmsghdr hdr;
|
||||
|
@ -620,7 +523,8 @@ TEST(NetlinkRouteTest, RecvmsgTrunc) {
|
|||
// it, so a properly sized buffer can be allocated to store the message. This
|
||||
// test tests that scenario.
|
||||
TEST(NetlinkRouteTest, RecvmsgTruncPeek) {
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
|
||||
|
||||
struct request {
|
||||
struct nlmsghdr hdr;
|
||||
|
@ -695,7 +599,8 @@ TEST(NetlinkRouteTest, RecvmsgTruncPeek) {
|
|||
|
||||
// No SCM_CREDENTIALS are received without SO_PASSCRED set.
|
||||
TEST(NetlinkRouteTest, NoPasscredNoCreds) {
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
|
||||
|
||||
ASSERT_THAT(setsockopt(fd.get(), SOL_SOCKET, SO_PASSCRED, &kSockOptOff,
|
||||
sizeof(kSockOptOff)),
|
||||
|
@ -742,7 +647,8 @@ TEST(NetlinkRouteTest, NoPasscredNoCreds) {
|
|||
|
||||
// SCM_CREDENTIALS are received with SO_PASSCRED set.
|
||||
TEST(NetlinkRouteTest, PasscredCreds) {
|
||||
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
|
||||
|
||||
ASSERT_THAT(setsockopt(fd.get(), SOL_SOCKET, SO_PASSCRED, &kSockOptOn,
|
||||
sizeof(kSockOptOn)),
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
#include <linux/filter.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/syscalls/linux/socket_netlink_util.h"
|
||||
#include "test/syscalls/linux/socket_test_util.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
// Tests for NETLINK_KOBJECT_UEVENT sockets.
|
||||
//
|
||||
// gVisor never sends any messages on these sockets, so we don't test the events
|
||||
// themselves.
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
// SO_PASSCRED can be enabled. Since no messages are sent in gVisor, we don't
|
||||
// actually test receiving credentials.
|
||||
TEST(NetlinkUeventTest, PassCred) {
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_KOBJECT_UEVENT));
|
||||
|
||||
EXPECT_THAT(setsockopt(fd.get(), SOL_SOCKET, SO_PASSCRED, &kSockOptOn,
|
||||
sizeof(kSockOptOn)),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
|
||||
// SO_DETACH_FILTER fails without a filter already installed.
|
||||
TEST(NetlinkUeventTest, DetachNoFilter) {
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_KOBJECT_UEVENT));
|
||||
|
||||
int opt;
|
||||
EXPECT_THAT(
|
||||
setsockopt(fd.get(), SOL_SOCKET, SO_DETACH_FILTER, &opt, sizeof(opt)),
|
||||
SyscallFailsWithErrno(ENOENT));
|
||||
}
|
||||
|
||||
// We can attach a BPF filter.
|
||||
TEST(NetlinkUeventTest, AttachFilter) {
|
||||
FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_KOBJECT_UEVENT));
|
||||
|
||||
// Minimal BPF program: a single ret.
|
||||
struct sock_filter filter = {0x6, 0, 0, 0};
|
||||
struct sock_fprog prog = {};
|
||||
prog.len = 1;
|
||||
prog.filter = &filter;
|
||||
|
||||
EXPECT_THAT(
|
||||
setsockopt(fd.get(), SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)),
|
||||
SyscallSucceeds());
|
||||
|
||||
int opt;
|
||||
EXPECT_THAT(
|
||||
setsockopt(fd.get(), SOL_SOCKET, SO_DETACH_FILTER, &opt, sizeof(opt)),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
@ -27,9 +26,9 @@
|
|||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
PosixErrorOr<FileDescriptor> NetlinkBoundSocket() {
|
||||
PosixErrorOr<FileDescriptor> NetlinkBoundSocket(int protocol) {
|
||||
FileDescriptor fd;
|
||||
ASSIGN_OR_RETURN_ERRNO(fd, Socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE));
|
||||
ASSIGN_OR_RETURN_ERRNO(fd, Socket(AF_NETLINK, SOCK_RAW, protocol));
|
||||
|
||||
struct sockaddr_nl addr = {};
|
||||
addr.nl_family = AF_NETLINK;
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
#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"
|
||||
|
@ -25,8 +24,8 @@
|
|||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
// Returns a bound NETLINK_ROUTE socket.
|
||||
PosixErrorOr<FileDescriptor> NetlinkBoundSocket();
|
||||
// Returns a bound netlink socket.
|
||||
PosixErrorOr<FileDescriptor> NetlinkBoundSocket(int protocol);
|
||||
|
||||
// Returns the port ID of the passed socket.
|
||||
PosixErrorOr<uint32_t> NetlinkPortID(int fd);
|
||||
|
|
Loading…
Reference in New Issue