1 | /* SPDX-License-Identifier: GPL-2.0 |
2 | * Copyright (c) 2018 Facebook |
3 | * |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of version 2 of the GNU General Public |
6 | * License as published by the Free Software Foundation. |
7 | */ |
8 | #include <linux/bpf.h> |
9 | #include <linux/if_link.h> |
10 | #include <assert.h> |
11 | #include <errno.h> |
12 | #include <signal.h> |
13 | #include <stdio.h> |
14 | #include <stdlib.h> |
15 | #include <string.h> |
16 | #include <net/if.h> |
17 | #include <arpa/inet.h> |
18 | #include <netinet/ether.h> |
19 | #include <unistd.h> |
20 | #include <time.h> |
21 | #include <bpf/bpf.h> |
22 | #include <bpf/libbpf.h> |
23 | |
24 | #define STATS_INTERVAL_S 2U |
25 | #define MAX_PCKT_SIZE 600 |
26 | |
27 | static int ifindex = -1; |
28 | static __u32 xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST; |
29 | static __u32 prog_id; |
30 | |
31 | static void int_exit(int sig) |
32 | { |
33 | __u32 curr_prog_id = 0; |
34 | |
35 | if (ifindex > -1) { |
36 | if (bpf_xdp_query_id(ifindex, xdp_flags, &curr_prog_id)) { |
37 | printf(format: "bpf_xdp_query_id failed\n" ); |
38 | exit(status: 1); |
39 | } |
40 | if (prog_id == curr_prog_id) |
41 | bpf_xdp_detach(ifindex, xdp_flags, NULL); |
42 | else if (!curr_prog_id) |
43 | printf(format: "couldn't find a prog id on a given iface\n" ); |
44 | else |
45 | printf(format: "program on interface changed, not removing\n" ); |
46 | } |
47 | exit(status: 0); |
48 | } |
49 | |
50 | /* simple "icmp packet too big sent" counter |
51 | */ |
52 | static void poll_stats(unsigned int map_fd, unsigned int kill_after_s) |
53 | { |
54 | time_t started_at = time(NULL); |
55 | __u64 value = 0; |
56 | int key = 0; |
57 | |
58 | |
59 | while (!kill_after_s || time(NULL) - started_at <= kill_after_s) { |
60 | sleep(STATS_INTERVAL_S); |
61 | |
62 | assert(bpf_map_lookup_elem(map_fd, &key, &value) == 0); |
63 | |
64 | printf(format: "icmp \"packet too big\" sent: %10llu pkts\n" , value); |
65 | } |
66 | } |
67 | |
68 | static void usage(const char *cmd) |
69 | { |
70 | printf(format: "Start a XDP prog which send ICMP \"packet too big\" \n" |
71 | "messages if ingress packet is bigger then MAX_SIZE bytes\n" ); |
72 | printf(format: "Usage: %s [...]\n" , cmd); |
73 | printf(format: " -i <ifname|ifindex> Interface\n" ); |
74 | printf(format: " -T <stop-after-X-seconds> Default: 0 (forever)\n" ); |
75 | printf(format: " -P <MAX_PCKT_SIZE> Default: %u\n" , MAX_PCKT_SIZE); |
76 | printf(format: " -S use skb-mode\n" ); |
77 | printf(format: " -N enforce native mode\n" ); |
78 | printf(format: " -F force loading prog\n" ); |
79 | printf(format: " -h Display this help\n" ); |
80 | } |
81 | |
82 | int main(int argc, char **argv) |
83 | { |
84 | unsigned char opt_flags[256] = {}; |
85 | const char *optstr = "i:T:P:SNFh" ; |
86 | struct bpf_prog_info info = {}; |
87 | __u32 info_len = sizeof(info); |
88 | unsigned int kill_after_s = 0; |
89 | int i, prog_fd, map_fd, opt; |
90 | struct bpf_program *prog; |
91 | struct bpf_object *obj; |
92 | __u32 max_pckt_size = 0; |
93 | __u32 key = 0; |
94 | char filename[256]; |
95 | int err; |
96 | |
97 | for (i = 0; i < strlen(s: optstr); i++) |
98 | if (optstr[i] != 'h' && 'a' <= optstr[i] && optstr[i] <= 'z') |
99 | opt_flags[(unsigned char)optstr[i]] = 1; |
100 | |
101 | while ((opt = getopt(argc: argc, argv: argv, shortopts: optstr)) != -1) { |
102 | |
103 | switch (opt) { |
104 | case 'i': |
105 | ifindex = if_nametoindex(ifname: optarg); |
106 | if (!ifindex) |
107 | ifindex = atoi(nptr: optarg); |
108 | break; |
109 | case 'T': |
110 | kill_after_s = atoi(nptr: optarg); |
111 | break; |
112 | case 'P': |
113 | max_pckt_size = atoi(nptr: optarg); |
114 | break; |
115 | case 'S': |
116 | xdp_flags |= XDP_FLAGS_SKB_MODE; |
117 | break; |
118 | case 'N': |
119 | /* default, set below */ |
120 | break; |
121 | case 'F': |
122 | xdp_flags &= ~XDP_FLAGS_UPDATE_IF_NOEXIST; |
123 | break; |
124 | default: |
125 | usage(cmd: argv[0]); |
126 | return 1; |
127 | } |
128 | opt_flags[opt] = 0; |
129 | } |
130 | |
131 | if (!(xdp_flags & XDP_FLAGS_SKB_MODE)) |
132 | xdp_flags |= XDP_FLAGS_DRV_MODE; |
133 | |
134 | for (i = 0; i < strlen(s: optstr); i++) { |
135 | if (opt_flags[(unsigned int)optstr[i]]) { |
136 | fprintf(stderr, format: "Missing argument -%c\n" , optstr[i]); |
137 | usage(cmd: argv[0]); |
138 | return 1; |
139 | } |
140 | } |
141 | |
142 | if (!ifindex) { |
143 | fprintf(stderr, format: "Invalid ifname\n" ); |
144 | return 1; |
145 | } |
146 | |
147 | snprintf(s: filename, maxlen: sizeof(filename), format: "%s_kern.o" , argv[0]); |
148 | |
149 | obj = bpf_object__open_file(filename, NULL); |
150 | if (libbpf_get_error(obj)) |
151 | return 1; |
152 | |
153 | prog = bpf_object__next_program(obj, NULL); |
154 | bpf_program__set_type(prog, BPF_PROG_TYPE_XDP); |
155 | |
156 | err = bpf_object__load(obj); |
157 | if (err) |
158 | return 1; |
159 | |
160 | prog_fd = bpf_program__fd(prog); |
161 | |
162 | /* static global var 'max_pcktsz' is accessible from .data section */ |
163 | if (max_pckt_size) { |
164 | map_fd = bpf_object__find_map_fd_by_name(obj, "xdp_adju.data" ); |
165 | if (map_fd < 0) { |
166 | printf(format: "finding a max_pcktsz map in obj file failed\n" ); |
167 | return 1; |
168 | } |
169 | bpf_map_update_elem(map_fd, &key, &max_pckt_size, BPF_ANY); |
170 | } |
171 | |
172 | /* fetch icmpcnt map */ |
173 | map_fd = bpf_object__find_map_fd_by_name(obj, "icmpcnt" ); |
174 | if (map_fd < 0) { |
175 | printf(format: "finding a icmpcnt map in obj file failed\n" ); |
176 | return 1; |
177 | } |
178 | |
179 | signal(SIGINT, handler: int_exit); |
180 | signal(SIGTERM, handler: int_exit); |
181 | |
182 | if (bpf_xdp_attach(ifindex, prog_fd, xdp_flags, NULL) < 0) { |
183 | printf(format: "link set xdp fd failed\n" ); |
184 | return 1; |
185 | } |
186 | |
187 | err = bpf_prog_get_info_by_fd(prog_fd, &info, &info_len); |
188 | if (err) { |
189 | printf(format: "can't get prog info - %s\n" , strerror(errno)); |
190 | return 1; |
191 | } |
192 | prog_id = info.id; |
193 | |
194 | poll_stats(map_fd, kill_after_s); |
195 | int_exit(sig: 0); |
196 | |
197 | return 0; |
198 | } |
199 | |