1 | // SPDX-License-Identifier: GPL-2.0 |
---|---|
2 | /* |
3 | * This testsuite provides conformance testing for GRO coalescing. |
4 | * |
5 | * Test cases: |
6 | * 1.data |
7 | * Data packets of the same size and same header setup with correct |
8 | * sequence numbers coalesce. The one exception being the last data |
9 | * packet coalesced: it can be smaller than the rest and coalesced |
10 | * as long as it is in the same flow. |
11 | * 2.ack |
12 | * Pure ACK does not coalesce. |
13 | * 3.flags |
14 | * Specific test cases: no packets with PSH, SYN, URG, RST set will |
15 | * be coalesced. |
16 | * 4.tcp |
17 | * Packets with incorrect checksum, non-consecutive seqno and |
18 | * different TCP header options shouldn't coalesce. Nit: given that |
19 | * some extension headers have paddings, such as timestamp, headers |
20 | * that are padding differently would not be coalesced. |
21 | * 5.ip: |
22 | * Packets with different (ECN, TTL, TOS) header, ip options or |
23 | * ip fragments (ipv6) shouldn't coalesce. |
24 | * 6.large: |
25 | * Packets larger than GRO_MAX_SIZE packets shouldn't coalesce. |
26 | * |
27 | * MSS is defined as 4096 - header because if it is too small |
28 | * (i.e. 1500 MTU - header), it will result in many packets, |
29 | * increasing the "large" test case's flakiness. This is because |
30 | * due to time sensitivity in the coalescing window, the receiver |
31 | * may not coalesce all of the packets. |
32 | * |
33 | * Note the timing issue applies to all of the test cases, so some |
34 | * flakiness is to be expected. |
35 | * |
36 | */ |
37 | |
38 | #define _GNU_SOURCE |
39 | |
40 | #include <arpa/inet.h> |
41 | #include <errno.h> |
42 | #include <error.h> |
43 | #include <getopt.h> |
44 | #include <linux/filter.h> |
45 | #include <linux/if_packet.h> |
46 | #include <linux/ipv6.h> |
47 | #include <net/ethernet.h> |
48 | #include <net/if.h> |
49 | #include <netinet/in.h> |
50 | #include <netinet/ip.h> |
51 | #include <netinet/ip6.h> |
52 | #include <netinet/tcp.h> |
53 | #include <stdbool.h> |
54 | #include <stddef.h> |
55 | #include <stdio.h> |
56 | #include <stdarg.h> |
57 | #include <string.h> |
58 | #include <unistd.h> |
59 | |
60 | #include "../kselftest.h" |
61 | |
62 | #define DPORT 8000 |
63 | #define SPORT 1500 |
64 | #define PAYLOAD_LEN 100 |
65 | #define NUM_PACKETS 4 |
66 | #define START_SEQ 100 |
67 | #define START_ACK 100 |
68 | #define ETH_P_NONE 0 |
69 | #define TOTAL_HDR_LEN (ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct tcphdr)) |
70 | #define MSS (4096 - sizeof(struct tcphdr) - sizeof(struct ipv6hdr)) |
71 | #define MAX_PAYLOAD (IP_MAXPACKET - sizeof(struct tcphdr) - sizeof(struct ipv6hdr)) |
72 | #define NUM_LARGE_PKT (MAX_PAYLOAD / MSS) |
73 | #define MAX_HDR_LEN (ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct tcphdr)) |
74 | #define MIN_EXTHDR_SIZE 8 |
75 | #define EXT_PAYLOAD_1 "\x00\x00\x00\x00\x00\x00" |
76 | #define EXT_PAYLOAD_2 "\x11\x11\x11\x11\x11\x11" |
77 | |
78 | #define ipv6_optlen(p) (((p)->hdrlen+1) << 3) /* calculate IPv6 extension header len */ |
79 | #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) |
80 | |
81 | static const char *addr6_src = "fdaa::2"; |
82 | static const char *addr6_dst = "fdaa::1"; |
83 | static const char *addr4_src = "192.168.1.200"; |
84 | static const char *addr4_dst = "192.168.1.100"; |
85 | static int proto = -1; |
86 | static uint8_t src_mac[ETH_ALEN], dst_mac[ETH_ALEN]; |
87 | static char *testname = "data"; |
88 | static char *ifname = "eth0"; |
89 | static char *smac = "aa:00:00:00:00:02"; |
90 | static char *dmac = "aa:00:00:00:00:01"; |
91 | static bool verbose; |
92 | static bool tx_socket = true; |
93 | static int tcp_offset = -1; |
94 | static int total_hdr_len = -1; |
95 | static int ethhdr_proto = -1; |
96 | static const int num_flush_id_cases = 6; |
97 | |
98 | static void vlog(const char *fmt, ...) |
99 | { |
100 | va_list args; |
101 | |
102 | if (verbose) { |
103 | va_start(args, fmt); |
104 | vfprintf(stderr, fmt, args); |
105 | va_end(args); |
106 | } |
107 | } |
108 | |
109 | static void setup_sock_filter(int fd) |
110 | { |
111 | const int dport_off = tcp_offset + offsetof(struct tcphdr, dest); |
112 | const int ethproto_off = offsetof(struct ethhdr, h_proto); |
113 | int optlen = 0; |
114 | int ipproto_off, opt_ipproto_off; |
115 | int next_off; |
116 | |
117 | if (proto == PF_INET) |
118 | next_off = offsetof(struct iphdr, protocol); |
119 | else |
120 | next_off = offsetof(struct ipv6hdr, nexthdr); |
121 | ipproto_off = ETH_HLEN + next_off; |
122 | |
123 | /* Overridden later if exthdrs are used: */ |
124 | opt_ipproto_off = ipproto_off; |
125 | |
126 | if (strcmp(testname, "ip") == 0) { |
127 | if (proto == PF_INET) |
128 | optlen = sizeof(struct ip_timestamp); |
129 | else { |
130 | BUILD_BUG_ON(sizeof(struct ip6_hbh) > MIN_EXTHDR_SIZE); |
131 | BUILD_BUG_ON(sizeof(struct ip6_dest) > MIN_EXTHDR_SIZE); |
132 | BUILD_BUG_ON(sizeof(struct ip6_frag) > MIN_EXTHDR_SIZE); |
133 | |
134 | /* same size for HBH and Fragment extension header types */ |
135 | optlen = MIN_EXTHDR_SIZE; |
136 | opt_ipproto_off = ETH_HLEN + sizeof(struct ipv6hdr) |
137 | + offsetof(struct ip6_ext, ip6e_nxt); |
138 | } |
139 | } |
140 | |
141 | /* this filter validates the following: |
142 | * - packet is IPv4/IPv6 according to the running test. |
143 | * - packet is TCP. Also handles the case of one extension header and then TCP. |
144 | * - checks the packet tcp dport equals to DPORT. Also handles the case of one |
145 | * extension header and then TCP. |
146 | */ |
147 | struct sock_filter filter[] = { |
148 | BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ethproto_off), |
149 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ntohs(ethhdr_proto), 0, 9), |
150 | BPF_STMT(BPF_LD + BPF_B + BPF_ABS, ipproto_off), |
151 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_TCP, 2, 0), |
152 | BPF_STMT(BPF_LD + BPF_B + BPF_ABS, opt_ipproto_off), |
153 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_TCP, 0, 5), |
154 | BPF_STMT(BPF_LD + BPF_H + BPF_ABS, dport_off), |
155 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DPORT, 2, 0), |
156 | BPF_STMT(BPF_LD + BPF_H + BPF_ABS, dport_off + optlen), |
157 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DPORT, 0, 1), |
158 | BPF_STMT(BPF_RET + BPF_K, 0xFFFFFFFF), |
159 | BPF_STMT(BPF_RET + BPF_K, 0), |
160 | }; |
161 | |
162 | struct sock_fprog bpf = { |
163 | .len = ARRAY_SIZE(filter), |
164 | .filter = filter, |
165 | }; |
166 | |
167 | if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)) < 0) |
168 | error(1, errno, "error setting filter"); |
169 | } |
170 | |
171 | static uint32_t checksum_nofold(void *data, size_t len, uint32_t sum) |
172 | { |
173 | uint16_t *words = data; |
174 | int i; |
175 | |
176 | for (i = 0; i < len / 2; i++) |
177 | sum += words[i]; |
178 | if (len & 1) |
179 | sum += ((char *)data)[len - 1]; |
180 | return sum; |
181 | } |
182 | |
183 | static uint16_t checksum_fold(void *data, size_t len, uint32_t sum) |
184 | { |
185 | sum = checksum_nofold(data, len, sum); |
186 | while (sum > 0xFFFF) |
187 | sum = (sum & 0xFFFF) + (sum >> 16); |
188 | return ~sum; |
189 | } |
190 | |
191 | static uint16_t tcp_checksum(void *buf, int payload_len) |
192 | { |
193 | struct pseudo_header6 { |
194 | struct in6_addr saddr; |
195 | struct in6_addr daddr; |
196 | uint16_t protocol; |
197 | uint16_t payload_len; |
198 | } ph6; |
199 | struct pseudo_header4 { |
200 | struct in_addr saddr; |
201 | struct in_addr daddr; |
202 | uint16_t protocol; |
203 | uint16_t payload_len; |
204 | } ph4; |
205 | uint32_t sum = 0; |
206 | |
207 | if (proto == PF_INET6) { |
208 | if (inet_pton(AF_INET6, addr6_src, &ph6.saddr) != 1) |
209 | error(1, errno, "inet_pton6 source ip pseudo"); |
210 | if (inet_pton(AF_INET6, addr6_dst, &ph6.daddr) != 1) |
211 | error(1, errno, "inet_pton6 dest ip pseudo"); |
212 | ph6.protocol = htons(IPPROTO_TCP); |
213 | ph6.payload_len = htons(sizeof(struct tcphdr) + payload_len); |
214 | |
215 | sum = checksum_nofold(data: &ph6, len: sizeof(ph6), sum: 0); |
216 | } else if (proto == PF_INET) { |
217 | if (inet_pton(AF_INET, addr4_src, &ph4.saddr) != 1) |
218 | error(1, errno, "inet_pton source ip pseudo"); |
219 | if (inet_pton(AF_INET, addr4_dst, &ph4.daddr) != 1) |
220 | error(1, errno, "inet_pton dest ip pseudo"); |
221 | ph4.protocol = htons(IPPROTO_TCP); |
222 | ph4.payload_len = htons(sizeof(struct tcphdr) + payload_len); |
223 | |
224 | sum = checksum_nofold(data: &ph4, len: sizeof(ph4), sum: 0); |
225 | } |
226 | |
227 | return checksum_fold(data: buf, len: sizeof(struct tcphdr) + payload_len, sum); |
228 | } |
229 | |
230 | static void read_MAC(uint8_t *mac_addr, char *mac) |
231 | { |
232 | if (sscanf(mac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", |
233 | &mac_addr[0], &mac_addr[1], &mac_addr[2], |
234 | &mac_addr[3], &mac_addr[4], &mac_addr[5]) != 6) |
235 | error(1, 0, "sscanf"); |
236 | } |
237 | |
238 | static void fill_datalinklayer(void *buf) |
239 | { |
240 | struct ethhdr *eth = buf; |
241 | |
242 | memcpy(eth->h_dest, dst_mac, ETH_ALEN); |
243 | memcpy(eth->h_source, src_mac, ETH_ALEN); |
244 | eth->h_proto = ethhdr_proto; |
245 | } |
246 | |
247 | static void fill_networklayer(void *buf, int payload_len) |
248 | { |
249 | struct ipv6hdr *ip6h = buf; |
250 | struct iphdr *iph = buf; |
251 | |
252 | if (proto == PF_INET6) { |
253 | memset(ip6h, 0, sizeof(*ip6h)); |
254 | |
255 | ip6h->version = 6; |
256 | ip6h->payload_len = htons(sizeof(struct tcphdr) + payload_len); |
257 | ip6h->nexthdr = IPPROTO_TCP; |
258 | ip6h->hop_limit = 8; |
259 | if (inet_pton(AF_INET6, addr6_src, &ip6h->saddr) != 1) |
260 | error(1, errno, "inet_pton source ip6"); |
261 | if (inet_pton(AF_INET6, addr6_dst, &ip6h->daddr) != 1) |
262 | error(1, errno, "inet_pton dest ip6"); |
263 | } else if (proto == PF_INET) { |
264 | memset(iph, 0, sizeof(*iph)); |
265 | |
266 | iph->version = 4; |
267 | iph->ihl = 5; |
268 | iph->ttl = 8; |
269 | iph->protocol = IPPROTO_TCP; |
270 | iph->tot_len = htons(sizeof(struct tcphdr) + |
271 | payload_len + sizeof(struct iphdr)); |
272 | iph->frag_off = htons(0x4000); /* DF = 1, MF = 0 */ |
273 | if (inet_pton(AF_INET, addr4_src, &iph->saddr) != 1) |
274 | error(1, errno, "inet_pton source ip"); |
275 | if (inet_pton(AF_INET, addr4_dst, &iph->daddr) != 1) |
276 | error(1, errno, "inet_pton dest ip"); |
277 | iph->check = checksum_fold(buf, sizeof(struct iphdr), 0); |
278 | } |
279 | } |
280 | |
281 | static void fill_transportlayer(void *buf, int seq_offset, int ack_offset, |
282 | int payload_len, int fin) |
283 | { |
284 | struct tcphdr *tcph = buf; |
285 | |
286 | memset(tcph, 0, sizeof(*tcph)); |
287 | |
288 | tcph->source = htons(SPORT); |
289 | tcph->dest = htons(DPORT); |
290 | tcph->seq = ntohl(START_SEQ + seq_offset); |
291 | tcph->ack_seq = ntohl(START_ACK + ack_offset); |
292 | tcph->ack = 1; |
293 | tcph->fin = fin; |
294 | tcph->doff = 5; |
295 | tcph->window = htons(TCP_MAXWIN); |
296 | tcph->urg_ptr = 0; |
297 | tcph->check = tcp_checksum(buf: tcph, payload_len); |
298 | } |
299 | |
300 | static void write_packet(int fd, char *buf, int len, struct sockaddr_ll *daddr) |
301 | { |
302 | int ret = -1; |
303 | |
304 | ret = sendto(fd, buf, len, 0, (struct sockaddr *)daddr, sizeof(*daddr)); |
305 | if (ret == -1) |
306 | error(1, errno, "sendto failure"); |
307 | if (ret != len) |
308 | error(1, errno, "sendto wrong length"); |
309 | } |
310 | |
311 | static void create_packet(void *buf, int seq_offset, int ack_offset, |
312 | int payload_len, int fin) |
313 | { |
314 | memset(buf, 0, total_hdr_len); |
315 | memset(buf + total_hdr_len, 'a', payload_len); |
316 | fill_transportlayer(buf: buf + tcp_offset, seq_offset, ack_offset, |
317 | payload_len, fin); |
318 | fill_networklayer(buf: buf + ETH_HLEN, payload_len); |
319 | fill_datalinklayer(buf); |
320 | } |
321 | |
322 | /* send one extra flag, not first and not last pkt */ |
323 | static void send_flags(int fd, struct sockaddr_ll *daddr, int psh, int syn, |
324 | int rst, int urg) |
325 | { |
326 | static char flag_buf[MAX_HDR_LEN + PAYLOAD_LEN]; |
327 | static char buf[MAX_HDR_LEN + PAYLOAD_LEN]; |
328 | int payload_len, pkt_size, flag, i; |
329 | struct tcphdr *tcph; |
330 | |
331 | payload_len = PAYLOAD_LEN * psh; |
332 | pkt_size = total_hdr_len + payload_len; |
333 | flag = NUM_PACKETS / 2; |
334 | |
335 | create_packet(buf: flag_buf, seq_offset: flag * payload_len, ack_offset: 0, payload_len, fin: 0); |
336 | |
337 | tcph = (struct tcphdr *)(flag_buf + tcp_offset); |
338 | tcph->psh = psh; |
339 | tcph->syn = syn; |
340 | tcph->rst = rst; |
341 | tcph->urg = urg; |
342 | tcph->check = 0; |
343 | tcph->check = tcp_checksum(buf: tcph, payload_len); |
344 | |
345 | for (i = 0; i < NUM_PACKETS + 1; i++) { |
346 | if (i == flag) { |
347 | write_packet(fd, buf: flag_buf, len: pkt_size, daddr); |
348 | continue; |
349 | } |
350 | create_packet(buf, seq_offset: i * PAYLOAD_LEN, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
351 | write_packet(fd, buf, len: total_hdr_len + PAYLOAD_LEN, daddr); |
352 | } |
353 | } |
354 | |
355 | /* Test for data of same length, smaller than previous |
356 | * and of different lengths |
357 | */ |
358 | static void send_data_pkts(int fd, struct sockaddr_ll *daddr, |
359 | int payload_len1, int payload_len2) |
360 | { |
361 | static char buf[ETH_HLEN + IP_MAXPACKET]; |
362 | |
363 | create_packet(buf: buf, seq_offset: 0, ack_offset: 0, payload_len: payload_len1, fin: 0); |
364 | write_packet(fd, buf: buf, len: total_hdr_len + payload_len1, daddr); |
365 | create_packet(buf: buf, seq_offset: payload_len1, ack_offset: 0, payload_len: payload_len2, fin: 0); |
366 | write_packet(fd, buf: buf, len: total_hdr_len + payload_len2, daddr); |
367 | } |
368 | |
369 | /* If incoming segments make tracked segment length exceed |
370 | * legal IP datagram length, do not coalesce |
371 | */ |
372 | static void send_large(int fd, struct sockaddr_ll *daddr, int remainder) |
373 | { |
374 | static char pkts[NUM_LARGE_PKT][TOTAL_HDR_LEN + MSS]; |
375 | static char last[TOTAL_HDR_LEN + MSS]; |
376 | static char new_seg[TOTAL_HDR_LEN + MSS]; |
377 | int i; |
378 | |
379 | for (i = 0; i < NUM_LARGE_PKT; i++) |
380 | create_packet(pkts[i], i * MSS, 0, MSS, 0); |
381 | create_packet(last, NUM_LARGE_PKT * MSS, 0, remainder, 0); |
382 | create_packet(new_seg, (NUM_LARGE_PKT + 1) * MSS, 0, remainder, 0); |
383 | |
384 | for (i = 0; i < NUM_LARGE_PKT; i++) |
385 | write_packet(fd, pkts[i], total_hdr_len + MSS, daddr); |
386 | write_packet(fd, buf: last, len: total_hdr_len + remainder, daddr); |
387 | write_packet(fd, buf: new_seg, len: total_hdr_len + remainder, daddr); |
388 | } |
389 | |
390 | /* Pure acks and dup acks don't coalesce */ |
391 | static void send_ack(int fd, struct sockaddr_ll *daddr) |
392 | { |
393 | static char buf[MAX_HDR_LEN]; |
394 | |
395 | create_packet(buf, seq_offset: 0, ack_offset: 0, payload_len: 0, fin: 0); |
396 | write_packet(fd, buf, len: total_hdr_len, daddr); |
397 | write_packet(fd, buf, len: total_hdr_len, daddr); |
398 | create_packet(buf, seq_offset: 0, ack_offset: 1, payload_len: 0, fin: 0); |
399 | write_packet(fd, buf, len: total_hdr_len, daddr); |
400 | } |
401 | |
402 | static void recompute_packet(char *buf, char *no_ext, int extlen) |
403 | { |
404 | struct tcphdr *tcphdr = (struct tcphdr *)(buf + tcp_offset); |
405 | struct ipv6hdr *ip6h = (struct ipv6hdr *)(buf + ETH_HLEN); |
406 | struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN); |
407 | |
408 | memmove(buf, no_ext, total_hdr_len); |
409 | memmove(buf + total_hdr_len + extlen, |
410 | no_ext + total_hdr_len, PAYLOAD_LEN); |
411 | |
412 | tcphdr->doff = tcphdr->doff + (extlen / 4); |
413 | tcphdr->check = 0; |
414 | tcphdr->check = tcp_checksum(buf: tcphdr, PAYLOAD_LEN + extlen); |
415 | if (proto == PF_INET) { |
416 | iph->tot_len = htons(ntohs(iph->tot_len) + extlen); |
417 | iph->check = 0; |
418 | iph->check = checksum_fold(iph, sizeof(struct iphdr), 0); |
419 | } else { |
420 | ip6h->payload_len = htons(ntohs(ip6h->payload_len) + extlen); |
421 | } |
422 | } |
423 | |
424 | static void tcp_write_options(char *buf, int kind, int ts) |
425 | { |
426 | struct tcp_option_ts { |
427 | uint8_t kind; |
428 | uint8_t len; |
429 | uint32_t tsval; |
430 | uint32_t tsecr; |
431 | } *opt_ts = (void *)buf; |
432 | struct tcp_option_window { |
433 | uint8_t kind; |
434 | uint8_t len; |
435 | uint8_t shift; |
436 | } *opt_window = (void *)buf; |
437 | |
438 | switch (kind) { |
439 | case TCPOPT_NOP: |
440 | buf[0] = TCPOPT_NOP; |
441 | break; |
442 | case TCPOPT_WINDOW: |
443 | memset(opt_window, 0, sizeof(struct tcp_option_window)); |
444 | opt_window->kind = TCPOPT_WINDOW; |
445 | opt_window->len = TCPOLEN_WINDOW; |
446 | opt_window->shift = 0; |
447 | break; |
448 | case TCPOPT_TIMESTAMP: |
449 | memset(opt_ts, 0, sizeof(struct tcp_option_ts)); |
450 | opt_ts->kind = TCPOPT_TIMESTAMP; |
451 | opt_ts->len = TCPOLEN_TIMESTAMP; |
452 | opt_ts->tsval = ts; |
453 | opt_ts->tsecr = 0; |
454 | break; |
455 | default: |
456 | error(1, 0, "unimplemented TCP option"); |
457 | break; |
458 | } |
459 | } |
460 | |
461 | /* TCP with options is always a permutation of {TS, NOP, NOP}. |
462 | * Implement different orders to verify coalescing stops. |
463 | */ |
464 | static void add_standard_tcp_options(char *buf, char *no_ext, int ts, int order) |
465 | { |
466 | switch (order) { |
467 | case 0: |
468 | tcp_write_options(buf: buf + total_hdr_len, kind: TCPOPT_NOP, ts: 0); |
469 | tcp_write_options(buf: buf + total_hdr_len + 1, kind: TCPOPT_NOP, ts: 0); |
470 | tcp_write_options(buf: buf + total_hdr_len + 2 /* two NOP opts */, |
471 | kind: TCPOPT_TIMESTAMP, ts); |
472 | break; |
473 | case 1: |
474 | tcp_write_options(buf: buf + total_hdr_len, kind: TCPOPT_NOP, ts: 0); |
475 | tcp_write_options(buf: buf + total_hdr_len + 1, |
476 | kind: TCPOPT_TIMESTAMP, ts); |
477 | tcp_write_options(buf: buf + total_hdr_len + 1 + TCPOLEN_TIMESTAMP, |
478 | kind: TCPOPT_NOP, ts: 0); |
479 | break; |
480 | case 2: |
481 | tcp_write_options(buf: buf + total_hdr_len, kind: TCPOPT_TIMESTAMP, ts); |
482 | tcp_write_options(buf: buf + total_hdr_len + TCPOLEN_TIMESTAMP + 1, |
483 | kind: TCPOPT_NOP, ts: 0); |
484 | tcp_write_options(buf + total_hdr_len + TCPOLEN_TIMESTAMP + 2, |
485 | TCPOPT_NOP, 0); |
486 | break; |
487 | default: |
488 | error(1, 0, "unknown order"); |
489 | break; |
490 | } |
491 | recompute_packet(buf, no_ext, TCPOLEN_TSTAMP_APPA); |
492 | } |
493 | |
494 | /* Packets with invalid checksum don't coalesce. */ |
495 | static void send_changed_checksum(int fd, struct sockaddr_ll *daddr) |
496 | { |
497 | static char buf[MAX_HDR_LEN + PAYLOAD_LEN]; |
498 | struct tcphdr *tcph = (struct tcphdr *)(buf + tcp_offset); |
499 | int pkt_size = total_hdr_len + PAYLOAD_LEN; |
500 | |
501 | create_packet(buf, seq_offset: 0, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
502 | write_packet(fd, buf, len: pkt_size, daddr); |
503 | |
504 | create_packet(buf, PAYLOAD_LEN, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
505 | tcph->check = tcph->check - 1; |
506 | write_packet(fd, buf, len: pkt_size, daddr); |
507 | } |
508 | |
509 | /* Packets with non-consecutive sequence number don't coalesce.*/ |
510 | static void send_changed_seq(int fd, struct sockaddr_ll *daddr) |
511 | { |
512 | static char buf[MAX_HDR_LEN + PAYLOAD_LEN]; |
513 | struct tcphdr *tcph = (struct tcphdr *)(buf + tcp_offset); |
514 | int pkt_size = total_hdr_len + PAYLOAD_LEN; |
515 | |
516 | create_packet(buf, seq_offset: 0, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
517 | write_packet(fd, buf, len: pkt_size, daddr); |
518 | |
519 | create_packet(buf, PAYLOAD_LEN, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
520 | tcph->seq = ntohl(htonl(tcph->seq) + 1); |
521 | tcph->check = 0; |
522 | tcph->check = tcp_checksum(buf: tcph, PAYLOAD_LEN); |
523 | write_packet(fd, buf, len: pkt_size, daddr); |
524 | } |
525 | |
526 | /* Packet with different timestamp option or different timestamps |
527 | * don't coalesce. |
528 | */ |
529 | static void send_changed_ts(int fd, struct sockaddr_ll *daddr) |
530 | { |
531 | static char buf[MAX_HDR_LEN + PAYLOAD_LEN]; |
532 | static char extpkt[sizeof(buf) + TCPOLEN_TSTAMP_APPA]; |
533 | int pkt_size = total_hdr_len + PAYLOAD_LEN + TCPOLEN_TSTAMP_APPA; |
534 | |
535 | create_packet(buf, seq_offset: 0, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
536 | add_standard_tcp_options(buf: extpkt, no_ext: buf, ts: 0, order: 0); |
537 | write_packet(fd, buf: extpkt, len: pkt_size, daddr); |
538 | |
539 | create_packet(buf, PAYLOAD_LEN, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
540 | add_standard_tcp_options(buf: extpkt, no_ext: buf, ts: 0, order: 0); |
541 | write_packet(fd, buf: extpkt, len: pkt_size, daddr); |
542 | |
543 | create_packet(buf, PAYLOAD_LEN * 2, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
544 | add_standard_tcp_options(buf: extpkt, no_ext: buf, ts: 100, order: 0); |
545 | write_packet(fd, buf: extpkt, len: pkt_size, daddr); |
546 | |
547 | create_packet(buf, PAYLOAD_LEN * 3, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
548 | add_standard_tcp_options(buf: extpkt, no_ext: buf, ts: 100, order: 1); |
549 | write_packet(fd, buf: extpkt, len: pkt_size, daddr); |
550 | |
551 | create_packet(buf, PAYLOAD_LEN * 4, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
552 | add_standard_tcp_options(buf: extpkt, no_ext: buf, ts: 100, order: 2); |
553 | write_packet(fd, buf: extpkt, len: pkt_size, daddr); |
554 | } |
555 | |
556 | /* Packet with different tcp options don't coalesce. */ |
557 | static void send_diff_opt(int fd, struct sockaddr_ll *daddr) |
558 | { |
559 | static char buf[MAX_HDR_LEN + PAYLOAD_LEN]; |
560 | static char extpkt1[sizeof(buf) + TCPOLEN_TSTAMP_APPA]; |
561 | static char extpkt2[sizeof(buf) + TCPOLEN_MAXSEG]; |
562 | int extpkt1_size = total_hdr_len + PAYLOAD_LEN + TCPOLEN_TSTAMP_APPA; |
563 | int extpkt2_size = total_hdr_len + PAYLOAD_LEN + TCPOLEN_MAXSEG; |
564 | |
565 | create_packet(buf, seq_offset: 0, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
566 | add_standard_tcp_options(buf: extpkt1, no_ext: buf, ts: 0, order: 0); |
567 | write_packet(fd, buf: extpkt1, len: extpkt1_size, daddr); |
568 | |
569 | create_packet(buf, PAYLOAD_LEN, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
570 | add_standard_tcp_options(buf: extpkt1, no_ext: buf, ts: 0, order: 0); |
571 | write_packet(fd, buf: extpkt1, len: extpkt1_size, daddr); |
572 | |
573 | create_packet(buf, PAYLOAD_LEN * 2, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
574 | tcp_write_options(extpkt2 + MAX_HDR_LEN, TCPOPT_NOP, 0); |
575 | tcp_write_options(extpkt2 + MAX_HDR_LEN + 1, TCPOPT_WINDOW, 0); |
576 | recompute_packet(extpkt2, buf, TCPOLEN_WINDOW + 1); |
577 | write_packet(fd, buf: extpkt2, len: extpkt2_size, daddr); |
578 | } |
579 | |
580 | static void add_ipv4_ts_option(void *buf, void *optpkt) |
581 | { |
582 | struct ip_timestamp *ts = (struct ip_timestamp *)(optpkt + tcp_offset); |
583 | int optlen = sizeof(struct ip_timestamp); |
584 | struct iphdr *iph; |
585 | |
586 | if (optlen % 4) |
587 | error(1, 0, "ipv4 timestamp length is not a multiple of 4B"); |
588 | |
589 | ts->ipt_code = IPOPT_TS; |
590 | ts->ipt_len = optlen; |
591 | ts->ipt_ptr = 5; |
592 | ts->ipt_flg = IPOPT_TS_TSONLY; |
593 | |
594 | memcpy(optpkt, buf, tcp_offset); |
595 | memcpy(optpkt + tcp_offset + optlen, buf + tcp_offset, |
596 | sizeof(struct tcphdr) + PAYLOAD_LEN); |
597 | |
598 | iph = (struct iphdr *)(optpkt + ETH_HLEN); |
599 | iph->ihl = 5 + (optlen / 4); |
600 | iph->tot_len = htons(ntohs(iph->tot_len) + optlen); |
601 | iph->check = 0; |
602 | iph->check = checksum_fold(iph, sizeof(struct iphdr) + optlen, 0); |
603 | } |
604 | |
605 | static void add_ipv6_exthdr(void *buf, void *optpkt, __u8 exthdr_type, char *ext_payload) |
606 | { |
607 | struct ipv6_opt_hdr *exthdr = (struct ipv6_opt_hdr *)(optpkt + tcp_offset); |
608 | struct ipv6hdr *iph = (struct ipv6hdr *)(optpkt + ETH_HLEN); |
609 | char *exthdr_payload_start = (char *)(exthdr + 1); |
610 | |
611 | exthdr->hdrlen = 0; |
612 | exthdr->nexthdr = IPPROTO_TCP; |
613 | |
614 | memcpy(exthdr_payload_start, ext_payload, MIN_EXTHDR_SIZE - sizeof(*exthdr)); |
615 | |
616 | memcpy(optpkt, buf, tcp_offset); |
617 | memcpy(optpkt + tcp_offset + MIN_EXTHDR_SIZE, buf + tcp_offset, |
618 | sizeof(struct tcphdr) + PAYLOAD_LEN); |
619 | |
620 | iph->nexthdr = exthdr_type; |
621 | iph->payload_len = htons(ntohs(iph->payload_len) + MIN_EXTHDR_SIZE); |
622 | } |
623 | |
624 | static void fix_ip4_checksum(struct iphdr *iph) |
625 | { |
626 | iph->check = 0; |
627 | iph->check = checksum_fold(iph, sizeof(struct iphdr), 0); |
628 | } |
629 | |
630 | static void send_flush_id_case(int fd, struct sockaddr_ll *daddr, int tcase) |
631 | { |
632 | static char buf1[MAX_HDR_LEN + PAYLOAD_LEN]; |
633 | static char buf2[MAX_HDR_LEN + PAYLOAD_LEN]; |
634 | static char buf3[MAX_HDR_LEN + PAYLOAD_LEN]; |
635 | bool send_three = false; |
636 | struct iphdr *iph1; |
637 | struct iphdr *iph2; |
638 | struct iphdr *iph3; |
639 | |
640 | iph1 = (struct iphdr *)(buf1 + ETH_HLEN); |
641 | iph2 = (struct iphdr *)(buf2 + ETH_HLEN); |
642 | iph3 = (struct iphdr *)(buf3 + ETH_HLEN); |
643 | |
644 | create_packet(buf: buf1, seq_offset: 0, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
645 | create_packet(buf: buf2, PAYLOAD_LEN, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
646 | create_packet(buf: buf3, PAYLOAD_LEN * 2, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
647 | |
648 | switch (tcase) { |
649 | case 0: /* DF=1, Incrementing - should coalesce */ |
650 | iph1->frag_off |= htons(IP_DF); |
651 | iph1->id = htons(8); |
652 | |
653 | iph2->frag_off |= htons(IP_DF); |
654 | iph2->id = htons(9); |
655 | break; |
656 | |
657 | case 1: /* DF=1, Fixed - should coalesce */ |
658 | iph1->frag_off |= htons(IP_DF); |
659 | iph1->id = htons(8); |
660 | |
661 | iph2->frag_off |= htons(IP_DF); |
662 | iph2->id = htons(8); |
663 | break; |
664 | |
665 | case 2: /* DF=0, Incrementing - should coalesce */ |
666 | iph1->frag_off &= ~htons(IP_DF); |
667 | iph1->id = htons(8); |
668 | |
669 | iph2->frag_off &= ~htons(IP_DF); |
670 | iph2->id = htons(9); |
671 | break; |
672 | |
673 | case 3: /* DF=0, Fixed - should not coalesce */ |
674 | iph1->frag_off &= ~htons(IP_DF); |
675 | iph1->id = htons(8); |
676 | |
677 | iph2->frag_off &= ~htons(IP_DF); |
678 | iph2->id = htons(8); |
679 | break; |
680 | |
681 | case 4: /* DF=1, two packets incrementing, and one fixed - should |
682 | * coalesce only the first two packets |
683 | */ |
684 | iph1->frag_off |= htons(IP_DF); |
685 | iph1->id = htons(8); |
686 | |
687 | iph2->frag_off |= htons(IP_DF); |
688 | iph2->id = htons(9); |
689 | |
690 | iph3->frag_off |= htons(IP_DF); |
691 | iph3->id = htons(9); |
692 | send_three = true; |
693 | break; |
694 | |
695 | case 5: /* DF=1, two packets fixed, and one incrementing - should |
696 | * coalesce only the first two packets |
697 | */ |
698 | iph1->frag_off |= htons(IP_DF); |
699 | iph1->id = htons(8); |
700 | |
701 | iph2->frag_off |= htons(IP_DF); |
702 | iph2->id = htons(8); |
703 | |
704 | iph3->frag_off |= htons(IP_DF); |
705 | iph3->id = htons(9); |
706 | send_three = true; |
707 | break; |
708 | } |
709 | |
710 | fix_ip4_checksum(iph: iph1); |
711 | fix_ip4_checksum(iph: iph2); |
712 | write_packet(fd, buf: buf1, len: total_hdr_len + PAYLOAD_LEN, daddr); |
713 | write_packet(fd, buf: buf2, len: total_hdr_len + PAYLOAD_LEN, daddr); |
714 | |
715 | if (send_three) { |
716 | fix_ip4_checksum(iph: iph3); |
717 | write_packet(fd, buf: buf3, len: total_hdr_len + PAYLOAD_LEN, daddr); |
718 | } |
719 | } |
720 | |
721 | static void test_flush_id(int fd, struct sockaddr_ll *daddr, char *fin_pkt) |
722 | { |
723 | for (int i = 0; i < num_flush_id_cases; i++) { |
724 | sleep(1); |
725 | send_flush_id_case(fd, daddr, tcase: i); |
726 | sleep(1); |
727 | write_packet(fd, buf: fin_pkt, len: total_hdr_len, daddr); |
728 | } |
729 | } |
730 | |
731 | static void send_ipv6_exthdr(int fd, struct sockaddr_ll *daddr, char *ext_data1, char *ext_data2) |
732 | { |
733 | static char buf[MAX_HDR_LEN + PAYLOAD_LEN]; |
734 | static char exthdr_pck[sizeof(buf) + MIN_EXTHDR_SIZE]; |
735 | |
736 | create_packet(buf, seq_offset: 0, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
737 | add_ipv6_exthdr(buf, optpkt: exthdr_pck, IPPROTO_HOPOPTS, ext_payload: ext_data1); |
738 | write_packet(fd, buf: exthdr_pck, len: total_hdr_len + PAYLOAD_LEN + MIN_EXTHDR_SIZE, daddr); |
739 | |
740 | create_packet(buf, PAYLOAD_LEN * 1, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
741 | add_ipv6_exthdr(buf, optpkt: exthdr_pck, IPPROTO_HOPOPTS, ext_payload: ext_data2); |
742 | write_packet(fd, buf: exthdr_pck, len: total_hdr_len + PAYLOAD_LEN + MIN_EXTHDR_SIZE, daddr); |
743 | } |
744 | |
745 | /* IPv4 options shouldn't coalesce */ |
746 | static void send_ip_options(int fd, struct sockaddr_ll *daddr) |
747 | { |
748 | static char buf[MAX_HDR_LEN + PAYLOAD_LEN]; |
749 | static char optpkt[sizeof(buf) + sizeof(struct ip_timestamp)]; |
750 | int optlen = sizeof(struct ip_timestamp); |
751 | int pkt_size = total_hdr_len + PAYLOAD_LEN + optlen; |
752 | |
753 | create_packet(buf, seq_offset: 0, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
754 | write_packet(fd, buf, len: total_hdr_len + PAYLOAD_LEN, daddr); |
755 | |
756 | create_packet(buf, PAYLOAD_LEN * 1, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
757 | add_ipv4_ts_option(buf, optpkt: optpkt); |
758 | write_packet(fd, buf: optpkt, len: pkt_size, daddr); |
759 | |
760 | create_packet(buf, PAYLOAD_LEN * 2, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
761 | write_packet(fd, buf, len: total_hdr_len + PAYLOAD_LEN, daddr); |
762 | } |
763 | |
764 | /* IPv4 fragments shouldn't coalesce */ |
765 | static void send_fragment4(int fd, struct sockaddr_ll *daddr) |
766 | { |
767 | static char buf[IP_MAXPACKET]; |
768 | struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN); |
769 | int pkt_size = total_hdr_len + PAYLOAD_LEN; |
770 | |
771 | create_packet(buf: buf, seq_offset: 0, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
772 | write_packet(fd, buf: buf, len: pkt_size, daddr); |
773 | |
774 | /* Once fragmented, packet would retain the total_len. |
775 | * Tcp header is prepared as if rest of data is in follow-up frags, |
776 | * but follow up frags aren't actually sent. |
777 | */ |
778 | memset(buf + total_hdr_len, 'a', PAYLOAD_LEN * 2); |
779 | fill_transportlayer(buf: buf + tcp_offset, PAYLOAD_LEN, ack_offset: 0, PAYLOAD_LEN * 2, fin: 0); |
780 | fill_networklayer(buf: buf + ETH_HLEN, PAYLOAD_LEN); |
781 | fill_datalinklayer(buf: buf); |
782 | |
783 | iph->frag_off = htons(0x6000); // DF = 1, MF = 1 |
784 | iph->check = 0; |
785 | iph->check = checksum_fold(iph, sizeof(struct iphdr), 0); |
786 | write_packet(fd, buf: buf, len: pkt_size, daddr); |
787 | } |
788 | |
789 | /* IPv4 packets with different ttl don't coalesce.*/ |
790 | static void send_changed_ttl(int fd, struct sockaddr_ll *daddr) |
791 | { |
792 | int pkt_size = total_hdr_len + PAYLOAD_LEN; |
793 | static char buf[MAX_HDR_LEN + PAYLOAD_LEN]; |
794 | struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN); |
795 | |
796 | create_packet(buf, seq_offset: 0, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
797 | write_packet(fd, buf, len: pkt_size, daddr); |
798 | |
799 | create_packet(buf, PAYLOAD_LEN, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
800 | iph->ttl = 7; |
801 | iph->check = 0; |
802 | iph->check = checksum_fold(iph, sizeof(struct iphdr), 0); |
803 | write_packet(fd, buf, len: pkt_size, daddr); |
804 | } |
805 | |
806 | /* Packets with different tos don't coalesce.*/ |
807 | static void send_changed_tos(int fd, struct sockaddr_ll *daddr) |
808 | { |
809 | int pkt_size = total_hdr_len + PAYLOAD_LEN; |
810 | static char buf[MAX_HDR_LEN + PAYLOAD_LEN]; |
811 | struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN); |
812 | struct ipv6hdr *ip6h = (struct ipv6hdr *)(buf + ETH_HLEN); |
813 | |
814 | create_packet(buf, seq_offset: 0, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
815 | write_packet(fd, buf, len: pkt_size, daddr); |
816 | |
817 | create_packet(buf, PAYLOAD_LEN, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
818 | if (proto == PF_INET) { |
819 | iph->tos = 1; |
820 | iph->check = 0; |
821 | iph->check = checksum_fold(iph, sizeof(struct iphdr), 0); |
822 | } else if (proto == PF_INET6) { |
823 | ip6h->priority = 0xf; |
824 | } |
825 | write_packet(fd, buf, len: pkt_size, daddr); |
826 | } |
827 | |
828 | /* Packets with different ECN don't coalesce.*/ |
829 | static void send_changed_ECN(int fd, struct sockaddr_ll *daddr) |
830 | { |
831 | int pkt_size = total_hdr_len + PAYLOAD_LEN; |
832 | static char buf[MAX_HDR_LEN + PAYLOAD_LEN]; |
833 | struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN); |
834 | |
835 | create_packet(buf, seq_offset: 0, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
836 | write_packet(fd, buf, len: pkt_size, daddr); |
837 | |
838 | create_packet(buf, PAYLOAD_LEN, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
839 | if (proto == PF_INET) { |
840 | buf[ETH_HLEN + 1] ^= 0x2; // ECN set to 10 |
841 | iph->check = 0; |
842 | iph->check = checksum_fold(iph, sizeof(struct iphdr), 0); |
843 | } else { |
844 | buf[ETH_HLEN + 1] ^= 0x20; // ECN set to 10 |
845 | } |
846 | write_packet(fd, buf, len: pkt_size, daddr); |
847 | } |
848 | |
849 | /* IPv6 fragments and packets with extensions don't coalesce.*/ |
850 | static void send_fragment6(int fd, struct sockaddr_ll *daddr) |
851 | { |
852 | static char buf[MAX_HDR_LEN + PAYLOAD_LEN]; |
853 | static char extpkt[MAX_HDR_LEN + PAYLOAD_LEN + |
854 | sizeof(struct ip6_frag)]; |
855 | struct ipv6hdr *ip6h = (struct ipv6hdr *)(buf + ETH_HLEN); |
856 | struct ip6_frag *frag = (void *)(extpkt + tcp_offset); |
857 | int extlen = sizeof(struct ip6_frag); |
858 | int bufpkt_len = total_hdr_len + PAYLOAD_LEN; |
859 | int extpkt_len = bufpkt_len + extlen; |
860 | int i; |
861 | |
862 | for (i = 0; i < 2; i++) { |
863 | create_packet(buf, PAYLOAD_LEN * i, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
864 | write_packet(fd, buf, len: bufpkt_len, daddr); |
865 | } |
866 | sleep(1); |
867 | create_packet(buf, PAYLOAD_LEN * 2, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
868 | memset(extpkt, 0, extpkt_len); |
869 | |
870 | ip6h->nexthdr = IPPROTO_FRAGMENT; |
871 | ip6h->payload_len = htons(ntohs(ip6h->payload_len) + extlen); |
872 | frag->ip6f_nxt = IPPROTO_TCP; |
873 | |
874 | memcpy(extpkt, buf, tcp_offset); |
875 | memcpy(extpkt + tcp_offset + extlen, buf + tcp_offset, |
876 | sizeof(struct tcphdr) + PAYLOAD_LEN); |
877 | write_packet(fd, buf: extpkt, len: extpkt_len, daddr); |
878 | |
879 | create_packet(buf, PAYLOAD_LEN * 3, ack_offset: 0, PAYLOAD_LEN, fin: 0); |
880 | write_packet(fd, buf, len: bufpkt_len, daddr); |
881 | } |
882 | |
883 | static void bind_packetsocket(int fd) |
884 | { |
885 | struct sockaddr_ll daddr = {}; |
886 | |
887 | daddr.sll_family = AF_PACKET; |
888 | daddr.sll_protocol = ethhdr_proto; |
889 | daddr.sll_ifindex = if_nametoindex(ifname); |
890 | if (daddr.sll_ifindex == 0) |
891 | error(1, errno, "if_nametoindex"); |
892 | |
893 | if (bind(fd, (void *)&daddr, sizeof(daddr)) < 0) |
894 | error(1, errno, "could not bind socket"); |
895 | } |
896 | |
897 | static void set_timeout(int fd) |
898 | { |
899 | struct timeval timeout; |
900 | |
901 | timeout.tv_sec = 3; |
902 | timeout.tv_usec = 0; |
903 | if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, |
904 | sizeof(timeout)) < 0) |
905 | error(1, errno, "cannot set timeout, setsockopt failed"); |
906 | } |
907 | |
908 | static void check_recv_pkts(int fd, int *correct_payload, |
909 | int correct_num_pkts) |
910 | { |
911 | static char buffer[IP_MAXPACKET + ETH_HLEN + 1]; |
912 | struct iphdr *iph = (struct iphdr *)(buffer + ETH_HLEN); |
913 | struct ipv6hdr *ip6h = (struct ipv6hdr *)(buffer + ETH_HLEN); |
914 | struct tcphdr *tcph; |
915 | bool bad_packet = false; |
916 | int tcp_ext_len = 0; |
917 | int ip_ext_len = 0; |
918 | int pkt_size = -1; |
919 | int data_len = 0; |
920 | int num_pkt = 0; |
921 | int i; |
922 | |
923 | vlog(fmt: "Expected {"); |
924 | for (i = 0; i < correct_num_pkts; i++) |
925 | vlog(fmt: "%d ", correct_payload[i]); |
926 | vlog(fmt: "}, Total %d packets\nReceived {", correct_num_pkts); |
927 | |
928 | while (1) { |
929 | ip_ext_len = 0; |
930 | pkt_size = recv(fd, buffer, IP_MAXPACKET + ETH_HLEN + 1, 0); |
931 | if (pkt_size < 0) |
932 | error(1, errno, "could not receive"); |
933 | |
934 | if (iph->version == 4) |
935 | ip_ext_len = (iph->ihl - 5) * 4; |
936 | else if (ip6h->version == 6 && ip6h->nexthdr != IPPROTO_TCP) |
937 | ip_ext_len = MIN_EXTHDR_SIZE; |
938 | |
939 | tcph = (struct tcphdr *)(buffer + tcp_offset + ip_ext_len); |
940 | |
941 | if (tcph->fin) |
942 | break; |
943 | |
944 | tcp_ext_len = (tcph->doff - 5) * 4; |
945 | data_len = pkt_size - total_hdr_len - tcp_ext_len - ip_ext_len; |
946 | /* Min ethernet frame payload is 46(ETH_ZLEN - ETH_HLEN) by RFC 802.3. |
947 | * Ipv4/tcp packets without at least 6 bytes of data will be padded. |
948 | * Packet sockets are protocol agnostic, and will not trim the padding. |
949 | */ |
950 | if (pkt_size == ETH_ZLEN && iph->version == 4) { |
951 | data_len = ntohs(iph->tot_len) |
952 | - sizeof(struct tcphdr) - sizeof(struct iphdr); |
953 | } |
954 | vlog(fmt: "%d ", data_len); |
955 | if (data_len != correct_payload[num_pkt]) { |
956 | vlog(fmt: "[!=%d]", correct_payload[num_pkt]); |
957 | bad_packet = true; |
958 | } |
959 | num_pkt++; |
960 | } |
961 | vlog(fmt: "}, Total %d packets.\n", num_pkt); |
962 | if (num_pkt != correct_num_pkts) |
963 | error(1, 0, "incorrect number of packets"); |
964 | if (bad_packet) |
965 | error(1, 0, "incorrect packet geometry"); |
966 | |
967 | printf("Test succeeded\n\n"); |
968 | } |
969 | |
970 | static void gro_sender(void) |
971 | { |
972 | static char fin_pkt[MAX_HDR_LEN]; |
973 | struct sockaddr_ll daddr = {}; |
974 | int txfd = -1; |
975 | |
976 | txfd = socket(PF_PACKET, SOCK_RAW, IPPROTO_RAW); |
977 | if (txfd < 0) |
978 | error(1, errno, "socket creation"); |
979 | |
980 | memset(&daddr, 0, sizeof(daddr)); |
981 | daddr.sll_ifindex = if_nametoindex(ifname); |
982 | if (daddr.sll_ifindex == 0) |
983 | error(1, errno, "if_nametoindex"); |
984 | daddr.sll_family = AF_PACKET; |
985 | memcpy(daddr.sll_addr, dst_mac, ETH_ALEN); |
986 | daddr.sll_halen = ETH_ALEN; |
987 | create_packet(buf: fin_pkt, PAYLOAD_LEN * 2, ack_offset: 0, payload_len: 0, fin: 1); |
988 | |
989 | if (strcmp(testname, "data") == 0) { |
990 | send_data_pkts(fd: txfd, daddr: &daddr, PAYLOAD_LEN, PAYLOAD_LEN); |
991 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
992 | |
993 | send_data_pkts(fd: txfd, daddr: &daddr, PAYLOAD_LEN, PAYLOAD_LEN / 2); |
994 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
995 | |
996 | send_data_pkts(fd: txfd, daddr: &daddr, PAYLOAD_LEN / 2, PAYLOAD_LEN); |
997 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
998 | } else if (strcmp(testname, "ack") == 0) { |
999 | send_ack(fd: txfd, daddr: &daddr); |
1000 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1001 | } else if (strcmp(testname, "flags") == 0) { |
1002 | send_flags(fd: txfd, daddr: &daddr, psh: 1, syn: 0, rst: 0, urg: 0); |
1003 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1004 | |
1005 | send_flags(fd: txfd, daddr: &daddr, psh: 0, syn: 1, rst: 0, urg: 0); |
1006 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1007 | |
1008 | send_flags(fd: txfd, daddr: &daddr, psh: 0, syn: 0, rst: 1, urg: 0); |
1009 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1010 | |
1011 | send_flags(fd: txfd, daddr: &daddr, psh: 0, syn: 0, rst: 0, urg: 1); |
1012 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1013 | } else if (strcmp(testname, "tcp") == 0) { |
1014 | send_changed_checksum(fd: txfd, daddr: &daddr); |
1015 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1016 | |
1017 | send_changed_seq(fd: txfd, daddr: &daddr); |
1018 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1019 | |
1020 | send_changed_ts(fd: txfd, daddr: &daddr); |
1021 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1022 | |
1023 | send_diff_opt(fd: txfd, daddr: &daddr); |
1024 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1025 | } else if (strcmp(testname, "ip") == 0) { |
1026 | send_changed_ECN(fd: txfd, daddr: &daddr); |
1027 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1028 | |
1029 | send_changed_tos(fd: txfd, daddr: &daddr); |
1030 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1031 | if (proto == PF_INET) { |
1032 | /* Modified packets may be received out of order. |
1033 | * Sleep function added to enforce test boundaries |
1034 | * so that fin pkts are not received prior to other pkts. |
1035 | */ |
1036 | sleep(1); |
1037 | send_changed_ttl(fd: txfd, daddr: &daddr); |
1038 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1039 | |
1040 | sleep(1); |
1041 | send_ip_options(fd: txfd, daddr: &daddr); |
1042 | sleep(1); |
1043 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1044 | |
1045 | sleep(1); |
1046 | send_fragment4(fd: txfd, daddr: &daddr); |
1047 | sleep(1); |
1048 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1049 | |
1050 | test_flush_id(fd: txfd, daddr: &daddr, fin_pkt); |
1051 | } else if (proto == PF_INET6) { |
1052 | sleep(1); |
1053 | send_fragment6(fd: txfd, daddr: &daddr); |
1054 | sleep(1); |
1055 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1056 | |
1057 | sleep(1); |
1058 | /* send IPv6 packets with ext header with same payload */ |
1059 | send_ipv6_exthdr(fd: txfd, daddr: &daddr, EXT_PAYLOAD_1, EXT_PAYLOAD_1); |
1060 | sleep(1); |
1061 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1062 | |
1063 | sleep(1); |
1064 | /* send IPv6 packets with ext header with different payload */ |
1065 | send_ipv6_exthdr(fd: txfd, daddr: &daddr, EXT_PAYLOAD_1, EXT_PAYLOAD_2); |
1066 | sleep(1); |
1067 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1068 | } |
1069 | } else if (strcmp(testname, "large") == 0) { |
1070 | /* 20 is the difference between min iphdr size |
1071 | * and min ipv6hdr size. Like MAX_HDR_SIZE, |
1072 | * MAX_PAYLOAD is defined with the larger header of the two. |
1073 | */ |
1074 | int offset = proto == PF_INET ? 20 : 0; |
1075 | int remainder = (MAX_PAYLOAD + offset) % MSS; |
1076 | |
1077 | send_large(fd: txfd, daddr: &daddr, remainder); |
1078 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1079 | |
1080 | send_large(fd: txfd, daddr: &daddr, remainder: remainder + 1); |
1081 | write_packet(fd: txfd, buf: fin_pkt, len: total_hdr_len, daddr: &daddr); |
1082 | } else { |
1083 | error(1, 0, "Unknown testcase"); |
1084 | } |
1085 | |
1086 | if (close(txfd)) |
1087 | error(1, errno, "socket close"); |
1088 | } |
1089 | |
1090 | static void gro_receiver(void) |
1091 | { |
1092 | static int correct_payload[NUM_PACKETS]; |
1093 | int rxfd = -1; |
1094 | |
1095 | rxfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_NONE)); |
1096 | if (rxfd < 0) |
1097 | error(1, 0, "socket creation"); |
1098 | setup_sock_filter(rxfd); |
1099 | set_timeout(rxfd); |
1100 | bind_packetsocket(fd: rxfd); |
1101 | |
1102 | memset(correct_payload, 0, sizeof(correct_payload)); |
1103 | |
1104 | if (strcmp(testname, "data") == 0) { |
1105 | printf("pure data packet of same size: "); |
1106 | correct_payload[0] = PAYLOAD_LEN * 2; |
1107 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 1); |
1108 | |
1109 | printf("large data packets followed by a smaller one: "); |
1110 | correct_payload[0] = PAYLOAD_LEN * 1.5; |
1111 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 1); |
1112 | |
1113 | printf("small data packets followed by a larger one: "); |
1114 | correct_payload[0] = PAYLOAD_LEN / 2; |
1115 | correct_payload[1] = PAYLOAD_LEN; |
1116 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 2); |
1117 | } else if (strcmp(testname, "ack") == 0) { |
1118 | printf("duplicate ack and pure ack: "); |
1119 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 3); |
1120 | } else if (strcmp(testname, "flags") == 0) { |
1121 | correct_payload[0] = PAYLOAD_LEN * 3; |
1122 | correct_payload[1] = PAYLOAD_LEN * 2; |
1123 | |
1124 | printf("psh flag ends coalescing: "); |
1125 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 2); |
1126 | |
1127 | correct_payload[0] = PAYLOAD_LEN * 2; |
1128 | correct_payload[1] = 0; |
1129 | correct_payload[2] = PAYLOAD_LEN * 2; |
1130 | printf("syn flag ends coalescing: "); |
1131 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 3); |
1132 | |
1133 | printf("rst flag ends coalescing: "); |
1134 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 3); |
1135 | |
1136 | printf("urg flag ends coalescing: "); |
1137 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 3); |
1138 | } else if (strcmp(testname, "tcp") == 0) { |
1139 | correct_payload[0] = PAYLOAD_LEN; |
1140 | correct_payload[1] = PAYLOAD_LEN; |
1141 | correct_payload[2] = PAYLOAD_LEN; |
1142 | correct_payload[3] = PAYLOAD_LEN; |
1143 | |
1144 | printf("changed checksum does not coalesce: "); |
1145 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 2); |
1146 | |
1147 | printf("Wrong Seq number doesn't coalesce: "); |
1148 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 2); |
1149 | |
1150 | printf("Different timestamp doesn't coalesce: "); |
1151 | correct_payload[0] = PAYLOAD_LEN * 2; |
1152 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 4); |
1153 | |
1154 | printf("Different options doesn't coalesce: "); |
1155 | correct_payload[0] = PAYLOAD_LEN * 2; |
1156 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 2); |
1157 | } else if (strcmp(testname, "ip") == 0) { |
1158 | correct_payload[0] = PAYLOAD_LEN; |
1159 | correct_payload[1] = PAYLOAD_LEN; |
1160 | |
1161 | printf("different ECN doesn't coalesce: "); |
1162 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 2); |
1163 | |
1164 | printf("different tos doesn't coalesce: "); |
1165 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 2); |
1166 | |
1167 | if (proto == PF_INET) { |
1168 | printf("different ttl doesn't coalesce: "); |
1169 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 2); |
1170 | |
1171 | printf("ip options doesn't coalesce: "); |
1172 | correct_payload[2] = PAYLOAD_LEN; |
1173 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 3); |
1174 | |
1175 | printf("fragmented ip4 doesn't coalesce: "); |
1176 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 2); |
1177 | |
1178 | /* is_atomic checks */ |
1179 | printf("DF=1, Incrementing - should coalesce: "); |
1180 | correct_payload[0] = PAYLOAD_LEN * 2; |
1181 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 1); |
1182 | |
1183 | printf("DF=1, Fixed - should coalesce: "); |
1184 | correct_payload[0] = PAYLOAD_LEN * 2; |
1185 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 1); |
1186 | |
1187 | printf("DF=0, Incrementing - should coalesce: "); |
1188 | correct_payload[0] = PAYLOAD_LEN * 2; |
1189 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 1); |
1190 | |
1191 | printf("DF=0, Fixed - should not coalesce: "); |
1192 | correct_payload[0] = PAYLOAD_LEN; |
1193 | correct_payload[1] = PAYLOAD_LEN; |
1194 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 2); |
1195 | |
1196 | printf("DF=1, 2 Incrementing and one fixed - should coalesce only first 2 packets: "); |
1197 | correct_payload[0] = PAYLOAD_LEN * 2; |
1198 | correct_payload[1] = PAYLOAD_LEN; |
1199 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 2); |
1200 | |
1201 | printf("DF=1, 2 Fixed and one incrementing - should coalesce only first 2 packets: "); |
1202 | correct_payload[0] = PAYLOAD_LEN * 2; |
1203 | correct_payload[1] = PAYLOAD_LEN; |
1204 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 2); |
1205 | } else if (proto == PF_INET6) { |
1206 | /* GRO doesn't check for ipv6 hop limit when flushing. |
1207 | * Hence no corresponding test to the ipv4 case. |
1208 | */ |
1209 | printf("fragmented ip6 doesn't coalesce: "); |
1210 | correct_payload[0] = PAYLOAD_LEN * 2; |
1211 | correct_payload[1] = PAYLOAD_LEN; |
1212 | correct_payload[2] = PAYLOAD_LEN; |
1213 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 3); |
1214 | |
1215 | printf("ipv6 with ext header does coalesce: "); |
1216 | correct_payload[0] = PAYLOAD_LEN * 2; |
1217 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 1); |
1218 | |
1219 | printf("ipv6 with ext header with different payloads doesn't coalesce: "); |
1220 | correct_payload[0] = PAYLOAD_LEN; |
1221 | correct_payload[1] = PAYLOAD_LEN; |
1222 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 2); |
1223 | } |
1224 | } else if (strcmp(testname, "large") == 0) { |
1225 | int offset = proto == PF_INET ? 20 : 0; |
1226 | int remainder = (MAX_PAYLOAD + offset) % MSS; |
1227 | |
1228 | correct_payload[0] = (MAX_PAYLOAD + offset); |
1229 | correct_payload[1] = remainder; |
1230 | printf("Shouldn't coalesce if exceed IP max pkt size: "); |
1231 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 2); |
1232 | |
1233 | /* last segment sent individually, doesn't start new segment */ |
1234 | correct_payload[0] = correct_payload[0] - remainder; |
1235 | correct_payload[1] = remainder + 1; |
1236 | correct_payload[2] = remainder + 1; |
1237 | check_recv_pkts(fd: rxfd, correct_payload, correct_num_pkts: 3); |
1238 | } else { |
1239 | error(1, 0, "Test case error, should never trigger"); |
1240 | } |
1241 | |
1242 | if (close(rxfd)) |
1243 | error(1, 0, "socket close"); |
1244 | } |
1245 | |
1246 | static void parse_args(int argc, char **argv) |
1247 | { |
1248 | static const struct option opts[] = { |
1249 | { "daddr", required_argument, NULL, 'd' }, |
1250 | { "dmac", required_argument, NULL, 'D' }, |
1251 | { "iface", required_argument, NULL, 'i' }, |
1252 | { "ipv4", no_argument, NULL, '4' }, |
1253 | { "ipv6", no_argument, NULL, '6' }, |
1254 | { "rx", no_argument, NULL, 'r' }, |
1255 | { "saddr", required_argument, NULL, 's' }, |
1256 | { "smac", required_argument, NULL, 'S' }, |
1257 | { "test", required_argument, NULL, 't' }, |
1258 | { "verbose", no_argument, NULL, 'v' }, |
1259 | { 0, 0, 0, 0 } |
1260 | }; |
1261 | int c; |
1262 | |
1263 | while ((c = getopt_long(argc, argv, "46d:D:i:rs:S:t:v", opts, NULL)) != -1) { |
1264 | switch (c) { |
1265 | case '4': |
1266 | proto = PF_INET; |
1267 | ethhdr_proto = htons(ETH_P_IP); |
1268 | break; |
1269 | case '6': |
1270 | proto = PF_INET6; |
1271 | ethhdr_proto = htons(ETH_P_IPV6); |
1272 | break; |
1273 | case 'd': |
1274 | addr4_dst = addr6_dst = optarg; |
1275 | break; |
1276 | case 'D': |
1277 | dmac = optarg; |
1278 | break; |
1279 | case 'i': |
1280 | ifname = optarg; |
1281 | break; |
1282 | case 'r': |
1283 | tx_socket = false; |
1284 | break; |
1285 | case 's': |
1286 | addr4_src = addr6_src = optarg; |
1287 | break; |
1288 | case 'S': |
1289 | smac = optarg; |
1290 | break; |
1291 | case 't': |
1292 | testname = optarg; |
1293 | break; |
1294 | case 'v': |
1295 | verbose = true; |
1296 | break; |
1297 | default: |
1298 | error(1, 0, "%s invalid option %c\n", __func__, c); |
1299 | break; |
1300 | } |
1301 | } |
1302 | } |
1303 | |
1304 | int main(int argc, char **argv) |
1305 | { |
1306 | parse_args(argc, argv); |
1307 | |
1308 | if (proto == PF_INET) { |
1309 | tcp_offset = ETH_HLEN + sizeof(struct iphdr); |
1310 | total_hdr_len = tcp_offset + sizeof(struct tcphdr); |
1311 | } else if (proto == PF_INET6) { |
1312 | tcp_offset = ETH_HLEN + sizeof(struct ipv6hdr); |
1313 | total_hdr_len = MAX_HDR_LEN; |
1314 | } else { |
1315 | error(1, 0, "Protocol family is not ipv4 or ipv6"); |
1316 | } |
1317 | |
1318 | read_MAC(mac_addr: src_mac, mac: smac); |
1319 | read_MAC(mac_addr: dst_mac, mac: dmac); |
1320 | |
1321 | if (tx_socket) { |
1322 | gro_sender(); |
1323 | } else { |
1324 | /* Only the receiver exit status determines test success. */ |
1325 | gro_receiver(); |
1326 | fprintf(stderr, "Gro::%s test passed.\n", testname); |
1327 | } |
1328 | |
1329 | return 0; |
1330 | } |
1331 |
Definitions
- addr6_src
- addr6_dst
- addr4_src
- addr4_dst
- proto
- src_mac
- dst_mac
- testname
- ifname
- smac
- dmac
- verbose
- tx_socket
- tcp_offset
- total_hdr_len
- ethhdr_proto
- num_flush_id_cases
- vlog
- setup_sock_filter
- checksum_nofold
- checksum_fold
- tcp_checksum
- read_MAC
- fill_datalinklayer
- fill_networklayer
- fill_transportlayer
- write_packet
- create_packet
- send_flags
- send_data_pkts
- send_large
- send_ack
- recompute_packet
- tcp_write_options
- add_standard_tcp_options
- send_changed_checksum
- send_changed_seq
- send_changed_ts
- send_diff_opt
- add_ipv4_ts_option
- add_ipv6_exthdr
- fix_ip4_checksum
- send_flush_id_case
- test_flush_id
- send_ipv6_exthdr
- send_ip_options
- send_fragment4
- send_changed_ttl
- send_changed_tos
- send_changed_ECN
- send_fragment6
- bind_packetsocket
- set_timeout
- check_recv_pkts
- gro_sender
- gro_receiver
- parse_args
Improve your Profiling and Debugging skills
Find out more