mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
ip6tables: test initial state
Tests that we have the correct initial (empty) state for ip6tables. #3549 PiperOrigin-RevId: 327477657
This commit is contained in:
committed by
gVisor bot
parent
3fd4b83fa3
commit
5cf330106a
@@ -250,6 +250,10 @@ syscall_test(
|
||||
test = "//test/syscalls/linux:iptables_test",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
test = "//test/syscalls/linux:ip6tables_test",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
shard_count = 5,
|
||||
|
||||
@@ -1029,6 +1029,24 @@ cc_binary(
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "ip6tables_test",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"ip6tables.cc",
|
||||
],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":iptables_types",
|
||||
":socket_test_util",
|
||||
"//test/util:capability_util",
|
||||
"//test/util:file_descriptor",
|
||||
gtest,
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "itimer_test",
|
||||
testonly = 1,
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
// 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.
|
||||
|
||||
#include <linux/capability.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/syscalls/linux/iptables.h"
|
||||
#include "test/syscalls/linux/socket_test_util.h"
|
||||
#include "test/util/capability_util.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kNatTablename[] = "nat";
|
||||
constexpr char kErrorTarget[] = "ERROR";
|
||||
constexpr size_t kEmptyStandardEntrySize =
|
||||
sizeof(struct ip6t_entry) + sizeof(struct xt_standard_target);
|
||||
constexpr size_t kEmptyErrorEntrySize =
|
||||
sizeof(struct ip6t_entry) + sizeof(struct xt_error_target);
|
||||
|
||||
// This tests the initial state of a machine with empty ip6tables via
|
||||
// getsockopt(IP6T_SO_GET_INFO). We don't have a guarantee that the iptables are
|
||||
// empty when running in native, but we can test that gVisor has the same
|
||||
// initial state that a newly-booted Linux machine would have.
|
||||
TEST(IP6TablesTest, InitialInfo) {
|
||||
// TODO(gvisor.dev/issue/3549): Enable for ip6tables.
|
||||
SKIP_IF(true);
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
|
||||
|
||||
FileDescriptor sock =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_RAW, IPPROTO_RAW));
|
||||
|
||||
// Get info via sockopt.
|
||||
struct ipt_getinfo info = {};
|
||||
snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
|
||||
socklen_t info_size = sizeof(info);
|
||||
ASSERT_THAT(
|
||||
getsockopt(sock.get(), SOL_IPV6, IP6T_SO_GET_INFO, &info, &info_size),
|
||||
SyscallSucceeds());
|
||||
|
||||
// The nat table supports PREROUTING, and OUTPUT.
|
||||
unsigned int valid_hooks =
|
||||
(1 << NF_IP6_PRE_ROUTING) | (1 << NF_IP6_LOCAL_OUT) |
|
||||
(1 << NF_IP6_POST_ROUTING) | (1 << NF_IP6_LOCAL_IN);
|
||||
EXPECT_EQ(info.valid_hooks, valid_hooks);
|
||||
|
||||
// Each chain consists of an empty entry with a standard target..
|
||||
EXPECT_EQ(info.hook_entry[NF_IP6_PRE_ROUTING], 0);
|
||||
EXPECT_EQ(info.hook_entry[NF_IP6_LOCAL_IN], kEmptyStandardEntrySize);
|
||||
EXPECT_EQ(info.hook_entry[NF_IP6_LOCAL_OUT], kEmptyStandardEntrySize * 2);
|
||||
EXPECT_EQ(info.hook_entry[NF_IP6_POST_ROUTING], kEmptyStandardEntrySize * 3);
|
||||
|
||||
// The underflow points are the same as the entry points.
|
||||
EXPECT_EQ(info.underflow[NF_IP6_PRE_ROUTING], 0);
|
||||
EXPECT_EQ(info.underflow[NF_IP6_LOCAL_IN], kEmptyStandardEntrySize);
|
||||
EXPECT_EQ(info.underflow[NF_IP6_LOCAL_OUT], kEmptyStandardEntrySize * 2);
|
||||
EXPECT_EQ(info.underflow[NF_IP6_POST_ROUTING], kEmptyStandardEntrySize * 3);
|
||||
|
||||
// One entry for each chain, plus an error entry at the end.
|
||||
EXPECT_EQ(info.num_entries, 5);
|
||||
|
||||
EXPECT_EQ(info.size, 4 * kEmptyStandardEntrySize + kEmptyErrorEntrySize);
|
||||
EXPECT_EQ(strcmp(info.name, kNatTablename), 0);
|
||||
}
|
||||
|
||||
// This tests the initial state of a machine with empty ip6tables via
|
||||
// getsockopt(IP6T_SO_GET_ENTRIES). We don't have a guarantee that the iptables
|
||||
// are empty when running in native, but we can test that gVisor has the same
|
||||
// initial state that a newly-booted Linux machine would have.
|
||||
TEST(IP6TablesTest, InitialEntries) {
|
||||
// TODO(gvisor.dev/issue/3549): Enable for ip6tables.
|
||||
SKIP_IF(true);
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
|
||||
|
||||
FileDescriptor sock =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_RAW, IPPROTO_RAW));
|
||||
|
||||
// Get info via sockopt.
|
||||
struct ipt_getinfo info = {};
|
||||
snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
|
||||
socklen_t info_size = sizeof(info);
|
||||
ASSERT_THAT(
|
||||
getsockopt(sock.get(), SOL_IPV6, IP6T_SO_GET_INFO, &info, &info_size),
|
||||
SyscallSucceeds());
|
||||
|
||||
// Use info to get entries.
|
||||
socklen_t entries_size = sizeof(struct ip6t_get_entries) + info.size;
|
||||
struct ip6t_get_entries* entries =
|
||||
static_cast<struct ip6t_get_entries*>(malloc(entries_size));
|
||||
snprintf(entries->name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
|
||||
entries->size = info.size;
|
||||
ASSERT_THAT(getsockopt(sock.get(), SOL_IPV6, IP6T_SO_GET_ENTRIES, entries,
|
||||
&entries_size),
|
||||
SyscallSucceeds());
|
||||
|
||||
// Verify the name and size.
|
||||
ASSERT_EQ(info.size, entries->size);
|
||||
ASSERT_EQ(strcmp(entries->name, kNatTablename), 0);
|
||||
|
||||
// Verify that the entrytable is 4 entries with accept targets and no matches
|
||||
// followed by a single error target.
|
||||
size_t entry_offset = 0;
|
||||
while (entry_offset < entries->size) {
|
||||
struct ip6t_entry* entry = reinterpret_cast<struct ip6t_entry*>(
|
||||
reinterpret_cast<char*>(entries->entrytable) + entry_offset);
|
||||
|
||||
// ipv6 should be zeroed.
|
||||
struct ip6t_ip6 zeroed = {};
|
||||
ASSERT_EQ(memcmp(static_cast<void*>(&zeroed),
|
||||
static_cast<void*>(&entry->ipv6), sizeof(zeroed)),
|
||||
0);
|
||||
|
||||
// target_offset should be zero.
|
||||
EXPECT_EQ(entry->target_offset, sizeof(ip6t_entry));
|
||||
|
||||
if (entry_offset < kEmptyStandardEntrySize * 4) {
|
||||
// The first 4 entries are standard targets
|
||||
struct xt_standard_target* target =
|
||||
reinterpret_cast<struct xt_standard_target*>(entry->elems);
|
||||
EXPECT_EQ(entry->next_offset, kEmptyStandardEntrySize);
|
||||
EXPECT_EQ(target->target.u.user.target_size, sizeof(*target));
|
||||
EXPECT_EQ(strcmp(target->target.u.user.name, ""), 0);
|
||||
EXPECT_EQ(target->target.u.user.revision, 0);
|
||||
// This is what's returned for an accept verdict. I don't know why.
|
||||
EXPECT_EQ(target->verdict, -NF_ACCEPT - 1);
|
||||
} else {
|
||||
// The last entry is an error target
|
||||
struct xt_error_target* target =
|
||||
reinterpret_cast<struct xt_error_target*>(entry->elems);
|
||||
EXPECT_EQ(entry->next_offset, kEmptyErrorEntrySize);
|
||||
EXPECT_EQ(target->target.u.user.target_size, sizeof(*target));
|
||||
EXPECT_EQ(strcmp(target->target.u.user.name, kErrorTarget), 0);
|
||||
EXPECT_EQ(target->target.u.user.revision, 0);
|
||||
EXPECT_EQ(strcmp(target->errorname, kErrorTarget), 0);
|
||||
}
|
||||
|
||||
entry_offset += entry->next_offset;
|
||||
break;
|
||||
}
|
||||
|
||||
free(entries);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -67,7 +67,7 @@ TEST(IPTablesBasic, FailSockoptNonRaw) {
|
||||
struct ipt_getinfo info = {};
|
||||
snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
|
||||
socklen_t info_size = sizeof(info);
|
||||
EXPECT_THAT(getsockopt(sock, IPPROTO_IP, SO_GET_INFO, &info, &info_size),
|
||||
EXPECT_THAT(getsockopt(sock, IPPROTO_IP, IPT_SO_GET_INFO, &info, &info_size),
|
||||
SyscallFailsWithErrno(ENOPROTOOPT));
|
||||
|
||||
ASSERT_THAT(close(sock), SyscallSucceeds());
|
||||
@@ -112,7 +112,7 @@ TEST_F(IPTablesTest, InitialState) {
|
||||
struct ipt_getinfo info = {};
|
||||
snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
|
||||
socklen_t info_size = sizeof(info);
|
||||
ASSERT_THAT(getsockopt(s_, IPPROTO_IP, SO_GET_INFO, &info, &info_size),
|
||||
ASSERT_THAT(getsockopt(s_, IPPROTO_IP, IPT_SO_GET_INFO, &info, &info_size),
|
||||
SyscallSucceeds());
|
||||
|
||||
// The nat table supports PREROUTING, and OUTPUT.
|
||||
@@ -148,7 +148,7 @@ TEST_F(IPTablesTest, InitialState) {
|
||||
snprintf(entries->name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
|
||||
entries->size = info.size;
|
||||
ASSERT_THAT(
|
||||
getsockopt(s_, IPPROTO_IP, SO_GET_ENTRIES, entries, &entries_size),
|
||||
getsockopt(s_, IPPROTO_IP, IPT_SO_GET_ENTRIES, entries, &entries_size),
|
||||
SyscallSucceeds());
|
||||
|
||||
// Verify the name and size.
|
||||
|
||||
+118
-14
@@ -27,27 +27,32 @@
|
||||
|
||||
#include <linux/netfilter/x_tables.h>
|
||||
#include <linux/netfilter_ipv4.h>
|
||||
#include <linux/netfilter_ipv6.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <stdint.h>
|
||||
|
||||
//
|
||||
// IPv4 ABI.
|
||||
//
|
||||
|
||||
#define ipt_standard_target xt_standard_target
|
||||
#define ipt_entry_target xt_entry_target
|
||||
#define ipt_error_target xt_error_target
|
||||
|
||||
enum SockOpts {
|
||||
// For setsockopt.
|
||||
BASE_CTL = 64,
|
||||
SO_SET_REPLACE = BASE_CTL,
|
||||
SO_SET_ADD_COUNTERS,
|
||||
SO_SET_MAX = SO_SET_ADD_COUNTERS,
|
||||
IPT_BASE_CTL = 64,
|
||||
IPT_SO_SET_REPLACE = IPT_BASE_CTL,
|
||||
IPT_SO_SET_ADD_COUNTERS = IPT_BASE_CTL + 1,
|
||||
IPT_SO_SET_MAX = IPT_SO_SET_ADD_COUNTERS,
|
||||
|
||||
// For getsockopt.
|
||||
SO_GET_INFO = BASE_CTL,
|
||||
SO_GET_ENTRIES,
|
||||
SO_GET_REVISION_MATCH,
|
||||
SO_GET_REVISION_TARGET,
|
||||
SO_GET_MAX = SO_GET_REVISION_TARGET
|
||||
IPT_SO_GET_INFO = IPT_BASE_CTL,
|
||||
IPT_SO_GET_ENTRIES = IPT_BASE_CTL + 1,
|
||||
IPT_SO_GET_REVISION_MATCH = IPT_BASE_CTL + 2,
|
||||
IPT_SO_GET_REVISION_TARGET = IPT_BASE_CTL + 3,
|
||||
IPT_SO_GET_MAX = IPT_SO_GET_REVISION_TARGET
|
||||
};
|
||||
|
||||
// ipt_ip specifies basic matching criteria that can be applied by examining
|
||||
@@ -115,7 +120,7 @@ struct ipt_entry {
|
||||
unsigned char elems[0];
|
||||
};
|
||||
|
||||
// Passed to getsockopt(SO_GET_INFO).
|
||||
// Passed to getsockopt(IPT_SO_GET_INFO).
|
||||
struct ipt_getinfo {
|
||||
// The name of the table. The user only fills this in, the rest is filled in
|
||||
// when returning from getsockopt. Currently "nat" and "mangle" are supported.
|
||||
@@ -127,7 +132,7 @@ struct ipt_getinfo {
|
||||
unsigned int valid_hooks;
|
||||
|
||||
// The offset into the entry table for each valid hook. The entry table is
|
||||
// returned by getsockopt(SO_GET_ENTRIES).
|
||||
// returned by getsockopt(IPT_SO_GET_ENTRIES).
|
||||
unsigned int hook_entry[NF_IP_NUMHOOKS];
|
||||
|
||||
// For each valid hook, the underflow is the offset into the entry table to
|
||||
@@ -142,14 +147,14 @@ struct ipt_getinfo {
|
||||
unsigned int underflow[NF_IP_NUMHOOKS];
|
||||
|
||||
// The number of entries in the entry table returned by
|
||||
// getsockopt(SO_GET_ENTRIES).
|
||||
// getsockopt(IPT_SO_GET_ENTRIES).
|
||||
unsigned int num_entries;
|
||||
|
||||
// The size of the entry table returned by getsockopt(SO_GET_ENTRIES).
|
||||
// The size of the entry table returned by getsockopt(IPT_SO_GET_ENTRIES).
|
||||
unsigned int size;
|
||||
};
|
||||
|
||||
// Passed to getsockopt(SO_GET_ENTRIES).
|
||||
// Passed to getsockopt(IPT_SO_GET_ENTRIES).
|
||||
struct ipt_get_entries {
|
||||
// The name of the table. The user fills this in. Currently "nat" and "mangle"
|
||||
// are supported.
|
||||
@@ -195,4 +200,103 @@ struct ipt_replace {
|
||||
struct ipt_entry entries[0];
|
||||
};
|
||||
|
||||
//
|
||||
// IPv6 ABI.
|
||||
//
|
||||
|
||||
enum SockOpts6 {
|
||||
// For setsockopt.
|
||||
IP6T_BASE_CTL = 64,
|
||||
IP6T_SO_SET_REPLACE = IP6T_BASE_CTL,
|
||||
IP6T_SO_SET_ADD_COUNTERS = IP6T_BASE_CTL + 1,
|
||||
IP6T_SO_SET_MAX = IP6T_SO_SET_ADD_COUNTERS,
|
||||
|
||||
// For getsockopt.
|
||||
IP6T_SO_GET_INFO = IP6T_BASE_CTL,
|
||||
IP6T_SO_GET_ENTRIES = IP6T_BASE_CTL + 1,
|
||||
IP6T_SO_GET_REVISION_MATCH = IP6T_BASE_CTL + 4,
|
||||
IP6T_SO_GET_REVISION_TARGET = IP6T_BASE_CTL + 5,
|
||||
IP6T_SO_GET_MAX = IP6T_SO_GET_REVISION_TARGET
|
||||
};
|
||||
|
||||
// ip6t_ip6 specifies basic matching criteria that can be applied by examining
|
||||
// only the IP header of a packet.
|
||||
struct ip6t_ip6 {
|
||||
// Source IP address.
|
||||
struct in6_addr src;
|
||||
|
||||
// Destination IP address.
|
||||
struct in6_addr dst;
|
||||
|
||||
// Source IP address mask.
|
||||
struct in6_addr smsk;
|
||||
|
||||
// Destination IP address mask.
|
||||
struct in6_addr dmsk;
|
||||
|
||||
// Input interface.
|
||||
char iniface[IFNAMSIZ];
|
||||
|
||||
// Output interface.
|
||||
char outiface[IFNAMSIZ];
|
||||
|
||||
// Input interface mask.
|
||||
unsigned char iniface_mask[IFNAMSIZ];
|
||||
|
||||
// Output interface mask.
|
||||
unsigned char outiface_mask[IFNAMSIZ];
|
||||
|
||||
// Transport protocol.
|
||||
uint16_t proto;
|
||||
|
||||
// TOS.
|
||||
uint8_t tos;
|
||||
|
||||
// Flags.
|
||||
uint8_t flags;
|
||||
|
||||
// Inverse flags.
|
||||
uint8_t invflags;
|
||||
};
|
||||
|
||||
// ip6t_entry is an ip6tables rule.
|
||||
struct ip6t_entry {
|
||||
// Basic matching information used to match a packet's IP header.
|
||||
struct ip6t_ip6 ipv6;
|
||||
|
||||
// A caching field that isn't used by userspace.
|
||||
unsigned int nfcache;
|
||||
|
||||
// The number of bytes between the start of this entry and the rule's target.
|
||||
uint16_t target_offset;
|
||||
|
||||
// The total size of this rule, from the beginning of the entry to the end of
|
||||
// the target.
|
||||
uint16_t next_offset;
|
||||
|
||||
// A return pointer not used by userspace.
|
||||
unsigned int comefrom;
|
||||
|
||||
// Counters for packets and bytes, which we don't yet implement.
|
||||
struct xt_counters counters;
|
||||
|
||||
// The data for all this rules matches followed by the target. This runs
|
||||
// beyond the value of sizeof(struct ip6t_entry).
|
||||
unsigned char elems[0];
|
||||
};
|
||||
|
||||
// Passed to getsockopt(IP6T_SO_GET_ENTRIES).
|
||||
struct ip6t_get_entries {
|
||||
// The name of the table.
|
||||
char name[XT_TABLE_MAXNAMELEN];
|
||||
|
||||
// The size of the entry table in bytes. The user fills this in with the value
|
||||
// from struct ipt_getinfo.size.
|
||||
unsigned int size;
|
||||
|
||||
// The entries for the given table. This will run past the size defined by
|
||||
// sizeof(struct ip6t_get_entries).
|
||||
struct ip6t_entry entrytable[0];
|
||||
};
|
||||
|
||||
#endif // GVISOR_TEST_SYSCALLS_IPTABLES_TYPES_H_
|
||||
|
||||
Reference in New Issue
Block a user