298 lines · c
1// SPDX-License-Identifier: GPL-2.02// Copyright (c) 2018 Facebook3// Copyright (c) 2019 Cloudflare4// Copyright (c) 2020 Isovalent, Inc.5/*6 * Test that the socket assign program is able to redirect traffic towards a7 * socket, regardless of whether the port or address destination of the traffic8 * matches the port.9 */10 11#define _GNU_SOURCE12#include <fcntl.h>13#include <signal.h>14#include <stdlib.h>15#include <unistd.h>16 17#include "test_progs.h"18#include "network_helpers.h"19 20#define BIND_PORT 123421#define CONNECT_PORT 432122#define TEST_DADDR (0xC0A80203)23#define NS_SELF "/proc/self/ns/net"24#define SERVER_MAP_PATH "/sys/fs/bpf/tc/globals/server_map"25 26static int stop, duration;27 28static bool29configure_stack(void)30{31 char tc_version[128];32 char tc_cmd[BUFSIZ];33 char *prog;34 FILE *tc;35 36 /* Check whether tc is built with libbpf. */37 tc = popen("tc -V", "r");38 if (CHECK_FAIL(!tc))39 return false;40 if (CHECK_FAIL(!fgets(tc_version, sizeof(tc_version), tc)))41 return false;42 if (strstr(tc_version, ", libbpf "))43 prog = "test_sk_assign_libbpf.bpf.o";44 else45 prog = "test_sk_assign.bpf.o";46 if (CHECK_FAIL(pclose(tc)))47 return false;48 49 /* Move to a new networking namespace */50 if (CHECK_FAIL(unshare(CLONE_NEWNET)))51 return false;52 53 /* Configure necessary links, routes */54 if (CHECK_FAIL(system("ip link set dev lo up")))55 return false;56 if (CHECK_FAIL(system("ip route add local default dev lo")))57 return false;58 if (CHECK_FAIL(system("ip -6 route add local default dev lo")))59 return false;60 61 /* Load qdisc, BPF program */62 if (CHECK_FAIL(system("tc qdisc add dev lo clsact")))63 return false;64 sprintf(tc_cmd, "%s %s %s %s %s", "tc filter add dev lo ingress bpf",65 "direct-action object-file", prog,66 "section tc",67 (env.verbosity < VERBOSE_VERY) ? " 2>/dev/null" : "verbose");68 if (CHECK(system(tc_cmd), "BPF load failed;",69 "run with -vv for more info\n"))70 return false;71 72 return true;73}74 75static in_port_t76get_port(int fd)77{78 struct sockaddr_storage ss;79 socklen_t slen = sizeof(ss);80 in_port_t port = 0;81 82 if (CHECK_FAIL(getsockname(fd, (struct sockaddr *)&ss, &slen)))83 return port;84 85 switch (ss.ss_family) {86 case AF_INET:87 port = ((struct sockaddr_in *)&ss)->sin_port;88 break;89 case AF_INET6:90 port = ((struct sockaddr_in6 *)&ss)->sin6_port;91 break;92 default:93 CHECK(1, "Invalid address family", "%d\n", ss.ss_family);94 }95 return port;96}97 98static ssize_t99rcv_msg(int srv_client, int type)100{101 char buf[BUFSIZ];102 103 if (type == SOCK_STREAM)104 return read(srv_client, &buf, sizeof(buf));105 else106 return recvfrom(srv_client, &buf, sizeof(buf), 0, NULL, NULL);107}108 109static int110run_test(int server_fd, const struct sockaddr *addr, socklen_t len, int type)111{112 int client = -1, srv_client = -1;113 char buf[] = "testing";114 in_port_t port;115 int ret = 1;116 117 client = connect_to_addr(type, (struct sockaddr_storage *)addr, len, NULL);118 if (client == -1) {119 perror("Cannot connect to server");120 goto out;121 }122 123 if (type == SOCK_STREAM) {124 srv_client = accept(server_fd, NULL, NULL);125 if (CHECK_FAIL(srv_client == -1)) {126 perror("Can't accept connection");127 goto out;128 }129 } else {130 srv_client = server_fd;131 }132 if (CHECK_FAIL(write(client, buf, sizeof(buf)) != sizeof(buf))) {133 perror("Can't write on client");134 goto out;135 }136 if (CHECK_FAIL(rcv_msg(srv_client, type) != sizeof(buf))) {137 perror("Can't read on server");138 goto out;139 }140 141 port = get_port(srv_client);142 if (CHECK_FAIL(!port))143 goto out;144 /* SOCK_STREAM is connected via accept(), so the server's local address145 * will be the CONNECT_PORT rather than the BIND port that corresponds146 * to the listen socket. SOCK_DGRAM on the other hand is connectionless147 * so we can't really do the same check there; the server doesn't ever148 * create a socket with CONNECT_PORT.149 */150 if (type == SOCK_STREAM &&151 CHECK(port != htons(CONNECT_PORT), "Expected", "port %u but got %u",152 CONNECT_PORT, ntohs(port)))153 goto out;154 else if (type == SOCK_DGRAM &&155 CHECK(port != htons(BIND_PORT), "Expected",156 "port %u but got %u", BIND_PORT, ntohs(port)))157 goto out;158 159 ret = 0;160out:161 close(client);162 if (srv_client != server_fd)163 close(srv_client);164 if (ret)165 WRITE_ONCE(stop, 1);166 return ret;167}168 169static void170prepare_addr(struct sockaddr *addr, int family, __u16 port, bool rewrite_addr)171{172 struct sockaddr_in *addr4;173 struct sockaddr_in6 *addr6;174 175 switch (family) {176 case AF_INET:177 addr4 = (struct sockaddr_in *)addr;178 memset(addr4, 0, sizeof(*addr4));179 addr4->sin_family = family;180 addr4->sin_port = htons(port);181 if (rewrite_addr)182 addr4->sin_addr.s_addr = htonl(TEST_DADDR);183 else184 addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);185 break;186 case AF_INET6:187 addr6 = (struct sockaddr_in6 *)addr;188 memset(addr6, 0, sizeof(*addr6));189 addr6->sin6_family = family;190 addr6->sin6_port = htons(port);191 addr6->sin6_addr = in6addr_loopback;192 if (rewrite_addr)193 addr6->sin6_addr.s6_addr32[3] = htonl(TEST_DADDR);194 break;195 default:196 fprintf(stderr, "Invalid family %d", family);197 }198}199 200struct test_sk_cfg {201 const char *name;202 int family;203 struct sockaddr *addr;204 socklen_t len;205 int type;206 bool rewrite_addr;207};208 209#define TEST(NAME, FAMILY, TYPE, REWRITE) \210{ \211 .name = NAME, \212 .family = FAMILY, \213 .addr = (FAMILY == AF_INET) ? (struct sockaddr *)&addr4 \214 : (struct sockaddr *)&addr6, \215 .len = (FAMILY == AF_INET) ? sizeof(addr4) : sizeof(addr6), \216 .type = TYPE, \217 .rewrite_addr = REWRITE, \218}219 220void test_sk_assign(void)221{222 struct sockaddr_in addr4;223 struct sockaddr_in6 addr6;224 struct test_sk_cfg tests[] = {225 TEST("ipv4 tcp port redir", AF_INET, SOCK_STREAM, false),226 TEST("ipv4 tcp addr redir", AF_INET, SOCK_STREAM, true),227 TEST("ipv6 tcp port redir", AF_INET6, SOCK_STREAM, false),228 TEST("ipv6 tcp addr redir", AF_INET6, SOCK_STREAM, true),229 TEST("ipv4 udp port redir", AF_INET, SOCK_DGRAM, false),230 TEST("ipv4 udp addr redir", AF_INET, SOCK_DGRAM, true),231 TEST("ipv6 udp port redir", AF_INET6, SOCK_DGRAM, false),232 TEST("ipv6 udp addr redir", AF_INET6, SOCK_DGRAM, true),233 };234 __s64 server = -1;235 int server_map;236 int self_net;237 int i;238 239 self_net = open(NS_SELF, O_RDONLY);240 if (CHECK_FAIL(self_net < 0)) {241 perror("Unable to open "NS_SELF);242 return;243 }244 245 if (!configure_stack()) {246 perror("configure_stack");247 goto cleanup;248 }249 250 server_map = bpf_obj_get(SERVER_MAP_PATH);251 if (CHECK_FAIL(server_map < 0)) {252 perror("Unable to open " SERVER_MAP_PATH);253 goto cleanup;254 }255 256 for (i = 0; i < ARRAY_SIZE(tests) && !READ_ONCE(stop); i++) {257 struct test_sk_cfg *test = &tests[i];258 const struct sockaddr *addr;259 const int zero = 0;260 int err;261 262 if (!test__start_subtest(test->name))263 continue;264 prepare_addr(test->addr, test->family, BIND_PORT, false);265 addr = (const struct sockaddr *)test->addr;266 server = start_server_addr(test->type,267 (const struct sockaddr_storage *)addr,268 test->len, NULL);269 if (server == -1)270 goto close;271 272 err = bpf_map_update_elem(server_map, &zero, &server, BPF_ANY);273 if (CHECK_FAIL(err)) {274 perror("Unable to update server_map");275 goto close;276 }277 278 /* connect to unbound ports */279 prepare_addr(test->addr, test->family, CONNECT_PORT,280 test->rewrite_addr);281 if (run_test(server, addr, test->len, test->type))282 goto close;283 284 close(server);285 server = -1;286 }287 288close:289 close(server);290 close(server_map);291cleanup:292 if (CHECK_FAIL(unlink(SERVER_MAP_PATH)))293 perror("Unable to unlink " SERVER_MAP_PATH);294 if (CHECK_FAIL(setns(self_net, CLONE_NEWNET)))295 perror("Failed to setns("NS_SELF")");296 close(self_net);297}298