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:
Michael Pratt 2019-11-04 10:06:00 -08:00 committed by gVisor bot
parent 3b4f5445d0
commit b23b36e701
15 changed files with 431 additions and 124 deletions

View File

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

View File

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

View File

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

View File

@ -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",
],
)

View File

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

View File

@ -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",

View File

@ -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"
)

View File

@ -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")

View File

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

View File

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

View File

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

View File

@ -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)),

View File

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

View File

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

View File

@ -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);