summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
authorKevin Krakauer <krakauer@google.com>2020-08-19 11:55:21 -0700
committerAndrei Vagin <avagin@gmail.com>2020-09-09 17:53:10 -0700
commit2915cc7f49ed03466badb0e940b765837afe64d2 (patch)
tree6f6647763b5a6c07a2538805b1d7c4bdf5276327 /test
parent15f7c43b75f34635261df05003a4d58519bbe02e (diff)
ip6tables: test initial state
Tests that we have the correct initial (empty) state for ip6tables. #3549 PiperOrigin-RevId: 327477657
Diffstat (limited to 'test')
-rw-r--r--test/syscalls/BUILD4
-rw-r--r--test/syscalls/linux/BUILD18
-rw-r--r--test/syscalls/linux/ip6tables.cc163
-rw-r--r--test/syscalls/linux/iptables.cc6
-rw-r--r--test/syscalls/linux/iptables.h132
5 files changed, 306 insertions, 17 deletions
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD
index 0eadc6b08..d11412c55 100644
--- a/test/syscalls/BUILD
+++ b/test/syscalls/BUILD
@@ -251,6 +251,10 @@ syscall_test(
)
syscall_test(
+ test = "//test/syscalls/linux:ip6tables_test",
+)
+
+syscall_test(
size = "large",
shard_count = 5,
test = "//test/syscalls/linux:itimer_test",
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index 6299870bc..bd1d9584a 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -1030,6 +1030,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,
srcs = ["itimer.cc"],
diff --git a/test/syscalls/linux/ip6tables.cc b/test/syscalls/linux/ip6tables.cc
new file mode 100644
index 000000000..685e513f8
--- /dev/null
+++ b/test/syscalls/linux/ip6tables.cc
@@ -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
diff --git a/test/syscalls/linux/iptables.cc b/test/syscalls/linux/iptables.cc
index b8e4ece64..9b338d970 100644
--- a/test/syscalls/linux/iptables.cc
+++ b/test/syscalls/linux/iptables.cc
@@ -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.
diff --git a/test/syscalls/linux/iptables.h b/test/syscalls/linux/iptables.h
index 0719c60a4..d0fc10fea 100644
--- a/test/syscalls/linux/iptables.h
+++ b/test/syscalls/linux/iptables.h
@@ -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_