blob: 53afc98b060975d6329f0b11bebfd1d5165d68b2 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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 <arpa/inet.h>
#include <errno.h>
#include <linux/if_packet.h>
#include <linux/if_tun.h>
#include <net/if.h>
#include <poll.h>
#include <sched.h>
#include <sys/capability.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <gtest/gtest.h>
#include <android-base/unique_fd.h>
#define LOG_TAG "NetdTest"
#include "bpf/BpfMap.h"
#include "netdbpf/bpf_shared.h"
#include "OffloadUtils.h"
namespace android {
namespace net {
using base::unique_fd;
TEST(NetUtilsWrapperTest, TestFileCapabilities) {
errno = 0;
ASSERT_EQ(NULL, cap_get_file("/system/bin/netutils-wrapper-1.0"));
ASSERT_EQ(ENODATA, errno);
}
TEST(NetdSELinuxTest, CheckProperMTULabels) {
// Since we expect the egrep regexp to filter everything out,
// we thus expect no matches and thus a return code of 1
// NOLINTNEXTLINE(cert-env33-c)
ASSERT_EQ(W_EXITCODE(1, 0), system("ls -Z /sys/class/net/*/mtu | egrep -q -v "
"'^u:object_r:sysfs_net:s0 /sys/class/net/'"));
}
// Trivial thread function that simply immediately terminates successfully.
static int thread(void*) {
return 0;
}
typedef int (*thread_t)(void*);
static void nsTest(int flags, bool success, thread_t newThread) {
// We need a minimal stack, but not clear if it will grow up or down,
// So allocate 2 pages and give a pointer to the middle.
static char stack[PAGE_SIZE * 2];
errno = 0;
// VFORK: if thread is successfully created, then kernel will wait for it
// to terminate before we resume -> hence static stack is safe to reuse.
int tid = clone(newThread, &stack[PAGE_SIZE], flags | CLONE_VFORK, NULL);
if (success) {
ASSERT_EQ(errno, 0);
ASSERT_GE(tid, 0);
} else {
ASSERT_EQ(errno, EINVAL);
ASSERT_EQ(tid, -1);
}
}
// Test kernel configuration option CONFIG_NAMESPACES=y
TEST(NetdNamespaceTest, CheckMountNamespaceSupport) {
nsTest(CLONE_NEWNS, true, thread);
}
// Test kernel configuration option CONFIG_UTS_NS=y
TEST(NetdNamespaceTest, CheckUTSNamespaceSupport) {
nsTest(CLONE_NEWUTS, true, thread);
}
// Test kernel configuration option CONFIG_NET_NS=y
TEST(NetdNamespaceTest, CheckNetworkNamespaceSupport) {
nsTest(CLONE_NEWNET, true, thread);
}
// Test kernel configuration option CONFIG_USER_NS=n
TEST(NetdNamespaceTest, /*DISABLED_*/ CheckNoUserNamespaceSupport) {
nsTest(CLONE_NEWUSER, false, thread);
}
// Test for all of the above
TEST(NetdNamespaceTest, CheckFullNamespaceSupport) {
nsTest(CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWNET, true, thread);
}
// Test for presence of kernel patch:
// ANDROID: net: bpf: permit redirect from ingress L3 to egress L2 devices at near max mtu
// on 4.14+ kernels.
TEST(NetdBpfTest, testBpfSkbChangeHeadAboveMTU) {
SKIP_IF_EXTENDED_BPF_NOT_SUPPORTED;
constexpr int mtu = 1500;
errno = 0;
// Amusingly can't use SIOC... on tun/tap fds.
int rv = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0);
ASSERT_EQ(errno, 0);
ASSERT_GE(rv, 3);
unique_fd unixfd(rv);
rv = open("/dev/net/tun", O_RDWR | O_NONBLOCK);
ASSERT_EQ(errno, 0);
ASSERT_GE(rv, 3);
unique_fd tun(rv);
rv = open("/dev/net/tun", O_RDWR | O_NONBLOCK);
ASSERT_EQ(errno, 0);
ASSERT_GE(rv, 3);
unique_fd tap(rv);
struct ifreq tun_ifr = {
.ifr_flags = IFF_TUN | IFF_NO_PI,
.ifr_name = "tun_bpftest",
};
struct ifreq tap_ifr = {
.ifr_flags = IFF_TAP | IFF_NO_PI,
.ifr_name = "tap_bpftest",
};
rv = ioctl(tun, TUNSETIFF, &tun_ifr);
ASSERT_EQ(errno, 0);
ASSERT_EQ(rv, 0);
rv = ioctl(tap, TUNSETIFF, &tap_ifr);
ASSERT_EQ(errno, 0);
ASSERT_EQ(rv, 0);
// prevents kernel from sending us spurious ipv6 packets
rv = open("/proc/sys/net/ipv6/conf/tap_bpftest/disable_ipv6", O_WRONLY);
ASSERT_EQ(errno, 0);
ASSERT_GE(rv, 3);
unique_fd f(rv);
rv = write(f, "1\n", 2);
ASSERT_EQ(errno, 0);
ASSERT_EQ(rv, 2);
rv = close(f.release());
ASSERT_EQ(errno, 0);
ASSERT_EQ(rv, 0);
int tunif = if_nametoindex(tun_ifr.ifr_name);
ASSERT_GE(tunif, 2);
int tapif = if_nametoindex(tap_ifr.ifr_name);
ASSERT_GE(tapif, 2);
tun_ifr.ifr_mtu = mtu;
rv = ioctl(unixfd, SIOCSIFMTU, &tun_ifr);
ASSERT_EQ(errno, 0);
ASSERT_EQ(rv, 0);
tap_ifr.ifr_mtu = mtu;
rv = ioctl(unixfd, SIOCSIFMTU, &tap_ifr);
ASSERT_EQ(errno, 0);
ASSERT_EQ(rv, 0);
rv = ioctl(unixfd, SIOCGIFFLAGS, &tun_ifr);
ASSERT_EQ(errno, 0);
ASSERT_EQ(rv, 0);
rv = ioctl(unixfd, SIOCGIFFLAGS, &tap_ifr);
ASSERT_EQ(errno, 0);
ASSERT_EQ(rv, 0);
tun_ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
tap_ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
rv = ioctl(unixfd, SIOCSIFFLAGS, &tun_ifr);
ASSERT_EQ(errno, 0);
ASSERT_EQ(rv, 0);
rv = ioctl(unixfd, SIOCSIFFLAGS, &tap_ifr);
ASSERT_EQ(errno, 0);
ASSERT_EQ(rv, 0);
rv = tcQdiscAddDevClsact(tunif);
ASSERT_EQ(rv, 0);
int bpfFd = getTetherDownstream6TcProgFd(/* ethernet */ false);
ASSERT_EQ(errno, 0);
ASSERT_GE(bpfFd, 3);
rv = tcFilterAddDevIngress6Tether(tunif, bpfFd, /* ethernet */ false, DOWNSTREAM);
ASSERT_EQ(rv, 0);
bpf::BpfMap<TetherDownstream6Key, TetherDownstream6Value> bpfDownstream6Map;
bpf::BpfMap<TetherStatsKey, TetherStatsValue> bpfStatsMap;
bpf::BpfMap<TetherLimitKey, TetherLimitValue> bpfLimitMap;
rv = getTetherDownstream6MapFd();
ASSERT_GE(rv, 3);
bpfDownstream6Map.reset(rv);
rv = getTetherStatsMapFd();
ASSERT_GE(rv, 3);
bpfStatsMap.reset(rv);
rv = getTetherLimitMapFd();
ASSERT_GE(rv, 3);
bpfLimitMap.reset(rv);
TetherDownstream6Key key = {
.iif = static_cast<uint32_t>(tunif),
.neigh6 =
{
.s6_addr32 =
{
htonl(0x20010db8),
0,
0,
htonl(1),
},
},
};
ethhdr hdr = {
.h_proto = htons(ETH_P_IPV6),
};
TetherDownstream6Value value = {
.oif = static_cast<uint32_t>(tapif),
.macHeader = hdr,
.pmtu = mtu,
};
#define ASSERT_OK(status) ASSERT_TRUE((status).ok())
ASSERT_OK(bpfDownstream6Map.writeValue(key, value, BPF_ANY));
uint32_t k = tunif;
TetherStatsValue stats = {};
ASSERT_OK(bpfStatsMap.writeValue(k, stats, BPF_NOEXIST));
uint64_t limit = ~0uLL;
ASSERT_OK(bpfLimitMap.writeValue(k, limit, BPF_NOEXIST));
// minimal 'acceptable' 40-byte hoplimit 255 IPv6 packet, src ip 2000::, dst ip 2001:db8::1
uint8_t pkt[mtu] = {
// clang-format off
0x60, 0, 0, 0, 0, 40, 0, 255,
0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
// clang-format on
};
// Iterate over all packet sizes from minimal ipv6 packet to mtu.
// Tethering ebpf program should forward the packet from tun to tap interface.
// TUN is L3, TAP is L2, so it will add a 14 byte ethernet header.
for (int pkt_size = 40; pkt_size <= mtu; ++pkt_size) {
rv = write(tun, pkt, pkt_size);
ASSERT_EQ(errno, 0);
ASSERT_EQ(rv, pkt_size);
struct pollfd p = {
.fd = tap,
.events = POLLIN,
};
rv = poll(&p, 1, 1000 /*milliseconds*/);
if (rv == 0) {
// we hit a timeout at this packet size, log it
EXPECT_EQ(pkt_size, -1);
// this particular packet size is where it fails without the oneline kernel fix
if (pkt_size + ETH_HLEN == mtu + 1) EXPECT_EQ("detected missing kernel patch", "");
break;
}
EXPECT_EQ(errno, 0);
EXPECT_EQ(rv, 1);
EXPECT_EQ(p.revents, POLLIN);
// use a buffer 1 byte larger then what we expect so we don't simply get truncated down
uint8_t buf[ETH_HLEN + mtu + 1];
rv = read(tap, buf, sizeof(buf));
EXPECT_EQ(errno, 0);
EXPECT_EQ(rv, ETH_HLEN + pkt_size);
errno = 0;
if (rv < 0) break;
}
ASSERT_OK(bpfDownstream6Map.deleteValue(key));
ASSERT_OK(bpfStatsMap.deleteValue(k));
ASSERT_OK(bpfLimitMap.deleteValue(k));
}
} // namespace net
} // namespace android