Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/abi/linux/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ go_library(
"netfilter_ipv4.go",
"netfilter_ipv6.go",
"netlink.go",
"netlink_netfilter.go",
"netlink_route.go",
"nf_tables.go",
"poll.go",
Expand Down
95 changes: 95 additions & 0 deletions pkg/abi/linux/netlink_netfilter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2025 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 linux

// Group describes Netlink Netfilter groups, from uapi/linux/netfilter/nfnetlink.h.
// Users bind to specific groups to receive processing logs from those groups.
type Group uint16

// Netlink Netfilter groups.
const (
NFNLGPR_NONE Group = iota
NFNLGRP_CONNTRACK_NEW
NFNLGRP_CONNTRACK_UPDATE
NFNLGRP_CONNTRACK_DESTROY
NFNLGRP_CONNTRACK_EXP_NEW
NFNLGRP_CONNTRACK_EXP_UPDATE
NFNLGRP_CONNTRACK_EXP_DESTROY
NFNLGRP_NFTABLES
NFNLGRP_ACCT_QUOTA
NFNLGRP_NFTRACE
__NFNLGRP_MAX
NFNLGRP_MAX = __NFNLGRP_MAX - 1
)

// NetFilterGenMsg describes the netlink netfilter genmsg message, from uapi/linux/netfilter/nfnetlink.h.
type NetFilterGenMsg struct {
Family uint8
Version uint8
ResourceID uint16
}

// SizeOfNetfilterGenMsg is the size of the netlink netfilter genmsg message.
const SizeOfNetfilterGenMsg = 4

// NFNETLINK_V0 is the default version of the netlink netfilter.
const NFNETLINK_V0 = 0

// SubsysID describes Netlink Netfilter subsystem IDs, from uapi/linux/netfilter/nfnetlink.h.
type SubsysID uint16

// Netlink Netfilter subsystem IDs.
const (
NFNL_SUBSYS_NONE SubsysID = iota
NFNL_SUBSYS_CTNETLINK
NFNL_SUBSYS_CTNETLINK_EXP
NFNL_SUBSYS_QUEUE
NFNL_SUBSYS_ULOG
NFNL_SUBSYS_OSF
NFNL_SUBSYS_IPSET
NFNL_SUBSYS_ACCT
NFNL_SUBSYS_CTNETLINK_TIMEOUT
NFNL_SUBSYS_CTHELPER
NFNL_SUBSYS_NFTABLES
NFNL_SUBSYS_NFT_COMPAT
NFNL_SUBSYS_HOOK
)

// NetFilterSubsysID returns the Netfilter Subsystem ID from the netlink message header.
func (hdr *NetlinkMessageHeader) NetFilterSubsysID() SubsysID {
return SubsysID((hdr.Type & 0xff00) >> 8)
}

// NetFilterMsgType returns the Netfilter Message Type from the netlink message header.
func (hdr *NetlinkMessageHeader) NetFilterMsgType() NfTableMsgType {
return NfTableMsgType(hdr.Type & 0x00ff)
}

// Reserved control Netlink Netfilter messages, from uapi/linux/netfilter/nfnetlink.h.
const (
NFNL_MSG_BATCH_BEGIN = NLMSG_MIN_TYPE
NFNL_MSG_BATCH_END = NLMSG_MIN_TYPE + 1
)

// NetlinkBatchAttr describes Netlink Netfilter batch attributes, from uapi/linux/netfilter/nfnetlink.h.
type NetlinkBatchAttr uint16

// Netlink Netfilter batch attributes.
const (
NFNL_BATCH_UNSPEC NetlinkBatchAttr = iota
NFNL_BATCH_GENID
__NFNL_BATCH_MAX
NFNL_BATCH_MAX = __NFNL_BATCH_MAX - 1
)
43 changes: 43 additions & 0 deletions pkg/abi/linux/nf_tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,49 @@ const (
NFT_RETURN int32 = -5
)

// NfTableMsgType values map to operations within the nftables api.
// These correspond to values in include/uapi/linux/netfilter/nf_tables.h.
type NfTableMsgType uint16

// Netlink Netfilter table message types.
const (
NFT_MSG_NEWTABLE NfTableMsgType = iota
NFT_MSG_GETTABLE
NFT_MSG_DELTABLE
NFT_MSG_NEWCHAIN
NFT_MSG_GETCHAIN
NFT_MSG_DELCHAIN
NFT_MSG_NEWRULE
NFT_MSG_GETRULE
NFT_MSG_DELRULE
NFT_MSG_NEWSET
NFT_MSG_GETSET
NFT_MSG_DELSET
NFT_MSG_NEWSETELEM
NFT_MSG_GETSETELEM
NFT_MSG_DELSETELEM
NFT_MSG_NEWGEN
NFT_MSG_GETGEN
NFT_MSG_TRACE
NFT_MSG_NEWOBJ
NFT_MSG_GETOBJ
NFT_MSG_DELOBJ
NFT_MSG_GETOBJ_RESET
NFT_MSG_NEWFLOWTABLE
NFT_MSG_GETFLOWTABLE
NFT_MSG_DELFLOWTABLE
NFT_MSG_GETRULE_RESET
NFT_MSG_DESTROYTABLE
NFT_MSG_DESTROYCHAIN
NFT_MSG_DESTROYRULE
NFT_MSG_DESTROYSET
NFT_MSG_DESTROYSETELEM
NFT_MSG_DESTROYOBJ
NFT_MSG_DESTROYFLOWTABLE
NFT_MSG_GETSETELEM_RESET
NFT_MSG_MAX
)

// Nf table relational operators.
// Used by the nft comparison operation to compare values in registers.
// These correspond to enum values in include/uapi/linux/netfilter/nf_tables.h.
Expand Down
20 changes: 20 additions & 0 deletions pkg/sentry/socket/netlink/netfilter/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
load("//tools:defs.bzl", "go_library")

package(
default_applicable_licenses = ["//:license"],
licenses = ["notice"],
)

go_library(
name = "netfilter",
srcs = ["protocol.go"],
visibility = ["//pkg/sentry:internal"],
deps = [
"//pkg/abi/linux",
"//pkg/context",
"//pkg/sentry/kernel",
"//pkg/sentry/socket/netlink",
"//pkg/sentry/socket/netlink/nlmsg",
"//pkg/syserr",
],
)
76 changes: 76 additions & 0 deletions pkg/sentry/socket/netlink/netfilter/protocol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2025 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 netfilter provides a NETLINK_NETFILTER socket protocol.
package netfilter

import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/socket/netlink"
"gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg"
"gvisor.dev/gvisor/pkg/syserr"
)

// Protocol implements netlink.Protocol.
//
// +stateify savable
type Protocol struct{}

var _ netlink.Protocol = (*Protocol)(nil)

// NewProtocol creates a NETLINK_NETFILTER 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_NETFILTER
}

// CanSend implements netlink.Protocol.CanSend.
// Netfilter sockets should be able to send responses back, namely
// if a user wants to see the current state of the netfilter tables.
func (p *Protocol) CanSend() bool {
return true
}

// ProcessMessage implements netlink.Protocol.ProcessMessage.
func (p *Protocol) ProcessMessage(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
hdr := msg.Header()

// Netlink message payloads must be of at least the size of the genmsg. Return early if it is not,
// from linux/net/netfilter/nfnetlink.c.
if netLinkMessagePayloadSize(&hdr) < linux.SizeOfNetfilterGenMsg {
return nil
}

msgType := hdr.NetFilterMsgType()
// TODO: b/421437663 - Match the message type and call the appropriate Nftables function.
switch msgType {
default:
return syserr.ErrInvalidArgument
}
}

// init registers the NETLINK_NETFILTER provider.
func init() {
netlink.RegisterProvider(linux.NETLINK_NETFILTER, NewProtocol)
}

func netLinkMessagePayloadSize(h *linux.NetlinkMessageHeader) int {
return int(h.Length) - linux.NetlinkMessageHeaderSize
}
1 change: 1 addition & 0 deletions runsc/boot/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ go_library(
"//pkg/sentry/socket/hostinet",
"//pkg/sentry/socket/netfilter",
"//pkg/sentry/socket/netlink",
"//pkg/sentry/socket/netlink/netfilter",
"//pkg/sentry/socket/netlink/route",
"//pkg/sentry/socket/netlink/uevent",
"//pkg/sentry/socket/netstack",
Expand Down
1 change: 1 addition & 0 deletions runsc/boot/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ import (

// Include other supported socket providers.
_ "gvisor.dev/gvisor/pkg/sentry/socket/netlink"
_ "gvisor.dev/gvisor/pkg/sentry/socket/netlink/netfilter"
_ "gvisor.dev/gvisor/pkg/sentry/socket/netlink/route"
_ "gvisor.dev/gvisor/pkg/sentry/socket/netlink/uevent"
_ "gvisor.dev/gvisor/pkg/sentry/socket/unix"
Expand Down
5 changes: 5 additions & 0 deletions test/syscalls/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,11 @@ syscall_test(
test = "//test/syscalls/linux:socket_netlink_route_test",
)

syscall_test(
add_hostinet = True,
test = "//test/syscalls/linux:socket_netlink_netfilter_test",
)

syscall_test(
add_hostinet = True,
test = "//test/syscalls/linux:socket_netlink_uevent_test",
Expand Down
17 changes: 17 additions & 0 deletions test/syscalls/linux/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -3495,6 +3495,23 @@ cc_binary(
],
)

cc_binary(
name = "socket_netlink_netfilter_test",
testonly = 1,
srcs = ["socket_netlink_netfilter.cc"],
linkstatic = 1,
malloc = "//test/util:errno_safe_allocator",
deps = select_gtest() + [
":socket_netlink_util",
"//test/util:file_descriptor",
"//test/util:posix_error",
"//test/util:socket_util",
"//test/util:test_main",
"//test/util:test_util",
"@com_google_absl//absl/strings:str_format",
],
)

cc_binary(
name = "socket_netlink_uevent_test",
testonly = 1,
Expand Down
95 changes: 95 additions & 0 deletions test/syscalls/linux/socket_netlink_netfilter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2025 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/netfilter/nfnetlink.h>
#include <linux/netlink.h>
#include <sys/socket.h>

#include <functional>
#include <string>
#include <tuple>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/strings/str_format.h"
#include "test/syscalls/linux/socket_netlink_util.h"
#include "test/util/file_descriptor.h"
#include "test/util/posix_error.h"
#include "test/util/socket_util.h"
#include "test/util/test_util.h"

// Tests for NETLINK_NETFILTER sockets.

namespace gvisor {
namespace testing {

namespace {

using SockOptTest = ::testing::TestWithParam<
std::tuple<int, std::function<bool(int)>, std::string>>;

TEST_P(SockOptTest, GetSockOpt) {
int sockopt = std::get<0>(GetParam());
auto verifier = std::get<1>(GetParam());
std::string verifier_description = std::get<2>(GetParam());

FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(
Socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER));

int res;
socklen_t len = sizeof(res);

EXPECT_THAT(getsockopt(fd.get(), SOL_SOCKET, sockopt, &res, &len),
SyscallSucceeds());

EXPECT_EQ(len, sizeof(res));
EXPECT_TRUE(verifier(res)) << absl::StrFormat(
"getsockopt(%d, SOL_SOCKET, %d, &res, &len) => res=%d was unexpected, "
"expected %s",
fd.get(), sockopt, res, verifier_description);
}

std::function<bool(int)> IsPositive() {
return [](int val) { return val > 0; };
}

std::function<bool(int)> IsEqual(int target) {
return [target](int val) { return val == target; };
}

INSTANTIATE_TEST_SUITE_P(
NetlinkNetfilterTest, SockOptTest,
::testing::Values(
std::make_tuple(SO_SNDBUF, IsPositive(), "positive send buffer size"),
std::make_tuple(SO_RCVBUF, IsPositive(),
"positive receive buffer size"),
std::make_tuple(SO_TYPE, IsEqual(SOCK_RAW),
absl::StrFormat("SOCK_RAW (%d)", SOCK_RAW)),
std::make_tuple(SO_DOMAIN, IsEqual(AF_NETLINK),
absl::StrFormat("AF_NETLINK (%d)", AF_NETLINK)),
std::make_tuple(SO_PROTOCOL, IsEqual(NETLINK_NETFILTER),
absl::StrFormat("NETLINK_NETFILTER (%d)",
NETLINK_NETFILTER)),
std::make_tuple(SO_PASSCRED, IsEqual(0), "0")));

// Netlink sockets must be SOCK_DGRAM or SOCK_RAW.
TEST(NetlinkNetfilterTest, CanCreateSocket) {
FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_NETFILTER));
EXPECT_THAT(fd.get(), SyscallSucceeds());
}
} // namespace

} // namespace testing
} // namespace gvisor
Loading