| /* |
| * 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 |