1 // SPDX-License-Identifier: GPL-2.0
2 #include <test_progs.h>
3 #include <network_helpers.h>
4 #include <cgroup_helpers.h>
5 #include <linux/errqueue.h>
6 #include <poll.h>
7 #include <unistd.h>
8 #include "icmp_send.skel.h"
9
10 #define TIMEOUT_MS 1000
11
12 #define ICMP_DEST_UNREACH 3
13 #define ICMPV6_DEST_UNREACH 1
14
15 #define ICMP_HOST_UNREACH 1
16 #define ICMP_FRAG_NEEDED 4
17 #define NR_ICMP_UNREACH 15
18 #define ICMPV6_REJECT_ROUTE 6
19
20 #define KFUNC_RET_UNSET -1
21
connect_to_fd_nonblock(int server_fd)22 static int connect_to_fd_nonblock(int server_fd)
23 {
24 struct sockaddr_storage addr;
25 socklen_t len = sizeof(addr);
26 int fd, err, on = 1;
27
28 if (getsockname(server_fd, (struct sockaddr *)&addr, &len))
29 return -1;
30
31 fd = socket(addr.ss_family, SOCK_STREAM | SOCK_NONBLOCK, 0);
32 if (fd < 0)
33 return -1;
34
35 if (addr.ss_family == AF_INET6 &&
36 setsockopt(fd, IPPROTO_IPV6, IPV6_RECVERR, &on, sizeof(on)) < 0) {
37 close(fd);
38 return -1;
39 }
40
41 err = connect(fd, (struct sockaddr *)&addr, len);
42 if (err < 0 && errno != EINPROGRESS) {
43 close(fd);
44 return -1;
45 }
46
47 return fd;
48 }
49
read_icmp_errqueue(int sockfd,int expected_code,int af)50 static void read_icmp_errqueue(int sockfd, int expected_code, int af)
51 {
52 int expected_ee_type = (af == AF_INET) ? ICMP_DEST_UNREACH :
53 ICMPV6_DEST_UNREACH;
54 int expected_origin = (af == AF_INET) ? SO_EE_ORIGIN_ICMP :
55 SO_EE_ORIGIN_ICMP6;
56 int expected_level = (af == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;
57 int expected_type = (af == AF_INET) ? IP_RECVERR : IPV6_RECVERR;
58 struct sock_extended_err *sock_err;
59 char ctrl_buf[512];
60 struct msghdr msg = {
61 .msg_control = ctrl_buf,
62 .msg_controllen = sizeof(ctrl_buf),
63 };
64 struct pollfd pfd = {
65 .fd = sockfd,
66 .events = POLLERR,
67 };
68 struct cmsghdr *cm;
69 ssize_t n;
70
71 if (!ASSERT_GE(poll(&pfd, 1, TIMEOUT_MS), 1, "poll_errqueue"))
72 return;
73
74 n = recvmsg(sockfd, &msg, MSG_ERRQUEUE);
75 if (!ASSERT_GE(n, 0, "recvmsg_errqueue"))
76 return;
77
78 cm = CMSG_FIRSTHDR(&msg);
79 if (!ASSERT_NEQ(cm, NULL, "cm_firsthdr_null"))
80 return;
81
82 for (; cm; cm = CMSG_NXTHDR(&msg, cm)) {
83 if (cm->cmsg_level != expected_level ||
84 cm->cmsg_type != expected_type)
85 continue;
86
87 sock_err = (struct sock_extended_err *)CMSG_DATA(cm);
88
89 if (!ASSERT_EQ(sock_err->ee_origin, expected_origin,
90 "sock_err_origin"))
91 return;
92 if (!ASSERT_EQ(sock_err->ee_type, expected_ee_type,
93 "sock_err_type_dest_unreach"))
94 return;
95 ASSERT_EQ(sock_err->ee_code, expected_code, "sock_err_code");
96 return;
97 }
98
99 ASSERT_FAIL("no IP_RECVERR/IPV6_RECVERR control message found");
100 }
101
valid_unreach_code(int code,int af)102 static bool valid_unreach_code(int code, int af)
103 {
104 if (code < 0)
105 return false;
106
107 if (af == AF_INET)
108 return code <= NR_ICMP_UNREACH && code != ICMP_FRAG_NEEDED;
109
110 return code <= ICMPV6_REJECT_ROUTE;
111 }
112
trigger_prog_read_icmp_errqueue(struct icmp_send * skel,int code,int af,const char * ip)113 static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel, int code,
114 int af, const char *ip)
115 {
116 int srv_fd = -1, client_fd = -1;
117 int port;
118
119 srv_fd = start_server(af, SOCK_STREAM, ip, 0, TIMEOUT_MS);
120 if (!ASSERT_OK_FD(srv_fd, "start_server"))
121 return;
122
123 port = get_socket_local_port(srv_fd);
124 if (!ASSERT_GE(port, 0, "get_socket_local_port")) {
125 close(srv_fd);
126 return;
127 }
128
129 skel->bss->server_port = ntohs(port);
130 skel->bss->unreach_type = (af == AF_INET) ? ICMP_DEST_UNREACH :
131 ICMPV6_DEST_UNREACH;
132 skel->bss->unreach_code = code;
133 skel->data->kfunc_ret = KFUNC_RET_UNSET;
134
135 client_fd = connect_to_fd_nonblock(srv_fd);
136 if (!ASSERT_OK_FD(client_fd, "client_connect_nonblock")) {
137 close(srv_fd);
138 return;
139 }
140
141 if (valid_unreach_code(code, af))
142 read_icmp_errqueue(client_fd, code, af);
143
144 close(client_fd);
145 close(srv_fd);
146 }
147
run_icmp_test(struct icmp_send * skel,int af,const char * ip,int max_code)148 static void run_icmp_test(struct icmp_send *skel, int af, const char *ip,
149 int max_code)
150 {
151 for (int code = 0; code <= max_code; code++) {
152 if (af == AF_INET && code == ICMP_FRAG_NEEDED)
153 continue;
154
155 trigger_prog_read_icmp_errqueue(skel, code, af, ip);
156 ASSERT_EQ(skel->data->kfunc_ret, 0, "kfunc_ret");
157 }
158
159 /* Test invalid codes */
160 trigger_prog_read_icmp_errqueue(skel, -1, af, ip);
161 ASSERT_EQ(skel->data->kfunc_ret, -EINVAL, "kfunc_ret");
162
163 trigger_prog_read_icmp_errqueue(skel, max_code + 1, af, ip);
164 ASSERT_EQ(skel->data->kfunc_ret, -EINVAL, "kfunc_ret");
165
166 if (af == AF_INET) {
167 trigger_prog_read_icmp_errqueue(skel, ICMP_FRAG_NEEDED, af, ip);
168 ASSERT_EQ(skel->data->kfunc_ret, -EINVAL, "kfunc_ret");
169 }
170 }
171
run_icmp_no_route_test(struct icmp_send * skel,int af)172 static void run_icmp_no_route_test(struct icmp_send *skel, int af)
173 {
174 union {
175 struct ipv4_packet v4;
176 struct ipv6_packet v6;
177 } pkt;
178 DECLARE_LIBBPF_OPTS(bpf_test_run_opts, opts,
179 .data_in = &pkt,
180 );
181 int err;
182
183 switch (af) {
184 case AF_INET:
185 pkt.v4 = pkt_v4;
186 pkt.v4.iph.version = 4;
187 pkt.v4.iph.daddr = htonl(INADDR_LOOPBACK);
188 pkt.v4.tcp.dest = htons(80);
189 opts.data_size_in = sizeof(pkt.v4);
190 skel->bss->unreach_type = ICMP_DEST_UNREACH;
191 break;
192 case AF_INET6:
193 pkt.v6 = pkt_v6;
194 pkt.v6.iph.version = 6;
195 pkt.v6.iph.daddr = in6addr_loopback;
196 pkt.v6.tcp.dest = htons(80);
197 opts.data_size_in = sizeof(pkt.v6);
198 skel->bss->unreach_type = ICMPV6_DEST_UNREACH;
199 break;
200 default:
201 ASSERT_FAIL("af_not_supported");
202 return;
203 }
204
205 skel->bss->server_port = 80;
206 skel->data->kfunc_ret = KFUNC_RET_UNSET;
207
208 err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.egress), &opts);
209 if (!ASSERT_OK(err, "test_run"))
210 return;
211
212 ASSERT_EQ(skel->data->kfunc_ret, -ENETUNREACH, "kfunc_ret_no_route");
213 }
214
test_icmp_send_unreach_cgroup(void)215 void test_icmp_send_unreach_cgroup(void)
216 {
217 struct icmp_send *skel;
218 int cgroup_fd = -1;
219
220 skel = icmp_send__open_and_load();
221 if (!ASSERT_OK_PTR(skel, "skel_open"))
222 goto cleanup;
223
224 cgroup_fd = test__join_cgroup("/icmp_send_unreach_cgroup");
225 if (!ASSERT_OK_FD(cgroup_fd, "join_cgroup"))
226 goto cleanup;
227
228 skel->links.egress =
229 bpf_program__attach_cgroup(skel->progs.egress, cgroup_fd);
230 if (!ASSERT_OK_PTR(skel->links.egress, "prog_attach_cgroup"))
231 goto cleanup;
232
233 if (test__start_subtest("ipv4"))
234 run_icmp_test(skel, AF_INET, "127.0.0.1", NR_ICMP_UNREACH);
235
236 if (test__start_subtest("ipv6"))
237 run_icmp_test(skel, AF_INET6, "::1", ICMPV6_REJECT_ROUTE);
238
239 if (test__start_subtest("no_route_ipv4"))
240 run_icmp_no_route_test(skel, AF_INET);
241
242 if (test__start_subtest("no_route_ipv6"))
243 run_icmp_no_route_test(skel, AF_INET6);
244
245 cleanup:
246 icmp_send__destroy(skel);
247 if (cgroup_fd >= 0)
248 close(cgroup_fd);
249 }
250
test_icmp_send_unreach_recursion(void)251 void test_icmp_send_unreach_recursion(void)
252 {
253 struct icmp_send *skel;
254 int cgroup_fd = -1;
255 int err;
256
257 err = setup_cgroup_environment();
258 if (!ASSERT_OK(err, "setup_cgroup_environment"))
259 return;
260
261 skel = icmp_send__open_and_load();
262 if (!ASSERT_OK_PTR(skel, "skel_open"))
263 goto cleanup;
264
265 cgroup_fd = get_root_cgroup();
266 if (!ASSERT_OK_FD(cgroup_fd, "get_root_cgroup"))
267 goto cleanup;
268
269 skel->data->target_pid = getpid();
270 skel->links.recursion =
271 bpf_program__attach_cgroup(skel->progs.recursion, cgroup_fd);
272 if (!ASSERT_OK_PTR(skel->links.recursion, "prog_attach_cgroup"))
273 goto cleanup;
274
275 trigger_prog_read_icmp_errqueue(skel, ICMP_HOST_UNREACH, AF_INET,
276 "127.0.0.1");
277
278 /*
279 * Because there's recursion involved, the first call will return at
280 * index 1 since it will return the second, and the second call will
281 * return at index 0 since it will return the first.
282 */
283 ASSERT_EQ(skel->bss->rec_count, 2, "rec_count");
284 ASSERT_EQ(skel->data->rec_kfunc_rets[0], -EBUSY, "kfunc_rets[0]");
285 ASSERT_EQ(skel->data->rec_kfunc_rets[1], 0, "kfunc_rets[1]");
286
287 cleanup:
288 icmp_send__destroy(skel);
289 if (cgroup_fd >= 0)
290 close(cgroup_fd);
291 cleanup_cgroup_environment();
292 }
293