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