Skip to content

Commit 40ec30e

Browse files
mtardyKernel Patches Daemon
authored andcommitted
selftests/bpf: add icmp_send_unreach kfunc tests
This test opens a server and client, attach a cgroup_skb program on egress and calls the icmp_send_unreach function from the client egress so that an ICMP unreach control message is sent back to the client. It then fetches the message from the error queue to confirm the correct ICMP unreach code has been sent. Note that the BPF program returns SK_PASS to let the connection being established to finish the test cases quicker. Otherwise, you have to wait for the TCP three-way handshake to timeout in the kernel and retrieve the errno translated from the unreach code set by the ICMP control message. Signed-off-by: Mahe Tardy <[email protected]>
1 parent 60db5e7 commit 40ec30e

File tree

2 files changed

+135
-0
lines changed

2 files changed

+135
-0
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
#include <test_progs.h>
3+
#include <network_helpers.h>
4+
#include <linux/errqueue.h>
5+
#include "icmp_send_unreach.skel.h"
6+
7+
#define TIMEOUT_MS 1000
8+
#define SRV_PORT 54321
9+
10+
#define ICMP_DEST_UNREACH 3
11+
12+
#define ICMP_FRAG_NEEDED 4
13+
#define NR_ICMP_UNREACH 15
14+
15+
static void read_icmp_errqueue(int sockfd, int expected_code)
16+
{
17+
ssize_t n;
18+
struct sock_extended_err *sock_err;
19+
struct cmsghdr *cm;
20+
char ctrl_buf[512];
21+
struct msghdr msg = {
22+
.msg_control = ctrl_buf,
23+
.msg_controllen = sizeof(ctrl_buf),
24+
};
25+
26+
n = recvmsg(sockfd, &msg, MSG_ERRQUEUE);
27+
if (!ASSERT_GE(n, 0, "recvmsg_errqueue"))
28+
return;
29+
30+
for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm)) {
31+
if (!ASSERT_EQ(cm->cmsg_level, IPPROTO_IP, "cmsg_type") ||
32+
!ASSERT_EQ(cm->cmsg_type, IP_RECVERR, "cmsg_level"))
33+
continue;
34+
35+
sock_err = (struct sock_extended_err *)CMSG_DATA(cm);
36+
37+
if (!ASSERT_EQ(sock_err->ee_origin, SO_EE_ORIGIN_ICMP,
38+
"sock_err_origin_icmp"))
39+
return;
40+
if (!ASSERT_EQ(sock_err->ee_type, ICMP_DEST_UNREACH,
41+
"sock_err_type_dest_unreach"))
42+
return;
43+
ASSERT_EQ(sock_err->ee_code, expected_code, "sock_err_code");
44+
}
45+
}
46+
47+
void test_icmp_send_unreach_kfunc(void)
48+
{
49+
struct icmp_send_unreach *skel;
50+
int cgroup_fd = -1, client_fd = 1, srv_fd = -1;
51+
int *code;
52+
53+
skel = icmp_send_unreach__open_and_load();
54+
if (!ASSERT_OK_PTR(skel, "skel_open"))
55+
goto cleanup;
56+
57+
cgroup_fd = test__join_cgroup("/icmp_send_unreach_cgroup");
58+
if (!ASSERT_GE(cgroup_fd, 0, "join_cgroup"))
59+
goto cleanup;
60+
61+
skel->links.egress =
62+
bpf_program__attach_cgroup(skel->progs.egress, cgroup_fd);
63+
if (!ASSERT_OK_PTR(skel->links.egress, "prog_attach_cgroup"))
64+
goto cleanup;
65+
66+
code = &skel->bss->unreach_code;
67+
68+
for (*code = 0; *code <= NR_ICMP_UNREACH; (*code)++) {
69+
// The TCP stack reacts differently when asking for
70+
// fragmentation, let's ignore it for now
71+
if (*code == ICMP_FRAG_NEEDED)
72+
continue;
73+
74+
skel->bss->kfunc_ret = -1;
75+
76+
srv_fd = start_server(AF_INET, SOCK_STREAM, "127.0.0.1",
77+
SRV_PORT, TIMEOUT_MS);
78+
if (!ASSERT_GE(srv_fd, 0, "start_server"))
79+
goto for_cleanup;
80+
81+
client_fd = socket(AF_INET, SOCK_STREAM, 0);
82+
ASSERT_GE(client_fd, 0, "client_socket");
83+
84+
client_fd = connect_to_fd(srv_fd, 0);
85+
if (!ASSERT_GE(client_fd, 0, "client_connect"))
86+
goto for_cleanup;
87+
88+
read_icmp_errqueue(client_fd, *code);
89+
90+
ASSERT_EQ(skel->bss->kfunc_ret, SK_DROP, "kfunc_ret");
91+
for_cleanup:
92+
close(client_fd);
93+
close(srv_fd);
94+
}
95+
96+
cleanup:
97+
icmp_send_unreach__destroy(skel);
98+
close(cgroup_fd);
99+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
#include "vmlinux.h"
3+
#include <bpf/bpf_helpers.h>
4+
#include <bpf/bpf_endian.h>
5+
6+
char LICENSE[] SEC("license") = "Dual BSD/GPL";
7+
8+
int unreach_code = 0;
9+
int kfunc_ret = 0;
10+
11+
#define SERVER_PORT 54321
12+
#define SERVER_IP 0x7F000001
13+
14+
SEC("cgroup_skb/egress")
15+
int egress(struct __sk_buff *skb)
16+
{
17+
void *data = (void *)(long)skb->data;
18+
void *data_end = (void *)(long)skb->data_end;
19+
struct iphdr *iph;
20+
struct tcphdr *tcph;
21+
22+
iph = data;
23+
if ((void *)(iph + 1) > data_end || iph->version != 4 ||
24+
iph->protocol != IPPROTO_TCP || iph->daddr != bpf_htonl(SERVER_IP))
25+
return SK_PASS;
26+
27+
tcph = (void *)iph + iph->ihl * 4;
28+
if ((void *)(tcph + 1) > data_end ||
29+
tcph->dest != bpf_htons(SERVER_PORT))
30+
return SK_PASS;
31+
32+
kfunc_ret = bpf_icmp_send_unreach(skb, unreach_code);
33+
34+
/* returns SK_PASS to execute the test case quicker */
35+
return SK_PASS;
36+
}

0 commit comments

Comments
 (0)