Richard Gobert wrote:
Currently there is no test which checks that IPv6 extension header packets successfully coalesce. This commit adds a test, which verifies two IPv6 packets with HBH extension headers do coalesce.
I changed the receive socket filter to accept a packet with one extension header. This change exposed a bug in the fragment test -- the old BPF did not accept the fragment packet. I updated correct_num_packets in the fragment test accordingly.
Signed-off-by: Richard Gobert richardbgobert@gmail.com
Thanks for adding test coverage along with the feature, and as part of the existing gro test too.
tools/testing/selftests/net/gro.c | 78 ++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 7 deletions(-)
diff --git a/tools/testing/selftests/net/gro.c b/tools/testing/selftests/net/gro.c index 30024d0ed373..4ee34dca8e5f 100644 --- a/tools/testing/selftests/net/gro.c +++ b/tools/testing/selftests/net/gro.c @@ -71,6 +71,10 @@ #define MAX_PAYLOAD (IP_MAXPACKET - sizeof(struct tcphdr) - sizeof(struct ipv6hdr)) #define NUM_LARGE_PKT (MAX_PAYLOAD / MSS) #define MAX_HDR_LEN (ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct tcphdr)) +#define MIN_EXTHDR_SIZE 8 /* minimum size of IPv6 extension header */
+#define ipv6_optlen(p) (((p)->hdrlen+1) << 3) /* calculate IPv6 extension header len */
static const char *addr6_src = "fdaa::2"; static const char *addr6_dst = "fdaa::1"; @@ -104,7 +108,7 @@ static void setup_sock_filter(int fd) const int dport_off = tcp_offset + offsetof(struct tcphdr, dest); const int ethproto_off = offsetof(struct ethhdr, h_proto); int optlen = 0;
- int ipproto_off;
- int ipproto_off, opt_ipproto_off; int next_off;
if (proto == PF_INET) @@ -116,14 +120,27 @@ static void setup_sock_filter(int fd) if (strcmp(testname, "ip") == 0) { if (proto == PF_INET) optlen = sizeof(struct ip_timestamp);
else
optlen = sizeof(struct ip6_frag);
else {
// same size for HBH and Fragment extension header types
no C++ style comments
Also, instead of comments here and at the MIN_EXTHDR_SIZE definition, perhaps cleaner as self documenting code:
BUILD_BUG_ON(MIN_EXTHDR_SIZE != sizeof(struct ip6_hbh)); BUILD_BUG_ON(MIN_EXTHDR_SIZE != sizeof(struct ip6_dest)); BUILD_BUG_ON(MIN_EXTHDR_SIZE < sizeof(struct ip6_frag));
optlen = MIN_EXTHDR_SIZE;
opt_ipproto_off = ETH_HLEN + sizeof(struct ipv6hdr) +
offsetof(struct ip6_ext, ip6e_nxt);
}}
- /*
* this filter validates the following:
* - packet is IPv4/IPv6 according to the running test.
* - packet is TCP. Also handles the case of one extension header and then TCP.
* - checks the packet tcp dport equals to DPORT.
* (also handles the case of one extension header and then TCP.)
*/
nit: for filewide consistency: netdev style:
/* start comment * more comment */
struct sock_filter filter[] = { BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ethproto_off),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ntohs(ethhdr_proto), 0, 7),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ntohs(ethhdr_proto), 0, 9), BPF_STMT(BPF_LD + BPF_B + BPF_ABS, ipproto_off),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_TCP, 2, 0),
BPF_STMT(BPF_LD + BPF_B + BPF_ABS, opt_ipproto_off), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_TCP, 0, 5), BPF_STMT(BPF_LD + BPF_H + BPF_ABS, dport_off), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DPORT, 2, 0),
@@ -576,6 +593,39 @@ static void add_ipv4_ts_option(void *buf, void *optpkt) iph->check = checksum_fold(iph, sizeof(struct iphdr) + optlen, 0); } +static void add_ipv6_exthdr(void *buf, void *optpkt, __u8 exthdr_type) +{
- struct ipv6_opt_hdr *hbh_hdr = (struct ipv6_opt_hdr *)(optpkt + tcp_offset);
- struct ipv6hdr *iph = (struct ipv6hdr *)(optpkt + ETH_HLEN);
- int opt_len;
- hbh_hdr->hdrlen = 0;
- hbh_hdr->nexthdr = IPPROTO_TCP;
- opt_len = ipv6_optlen(hbh_hdr);
- memcpy(optpkt, buf, tcp_offset);
- memcpy(optpkt + tcp_offset + MIN_EXTHDR_SIZE, buf + tcp_offset,
sizeof(struct tcphdr) + PAYLOAD_LEN);
- iph->nexthdr = exthdr_type;
- iph->payload_len = htons(ntohs(iph->payload_len) + MIN_EXTHDR_SIZE);
+}
+/* Send IPv6 packet with extension header */ +static void send_ipv6_exthdr(int fd, struct sockaddr_ll *daddr) +{
- static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
- static char exthdr_pck[sizeof(buf) + MIN_EXTHDR_SIZE];
- create_packet(buf, 0, 0, PAYLOAD_LEN, 0);
- add_ipv6_exthdr(buf, exthdr_pck, IPPROTO_HOPOPTS);
- write_packet(fd, exthdr_pck, total_hdr_len + PAYLOAD_LEN + MIN_EXTHDR_SIZE, daddr);
- create_packet(buf, PAYLOAD_LEN * 1, 0, PAYLOAD_LEN, 0);
- add_ipv6_exthdr(buf, exthdr_pck, IPPROTO_HOPOPTS);
- write_packet(fd, exthdr_pck, total_hdr_len + PAYLOAD_LEN + MIN_EXTHDR_SIZE, daddr);
+}
/* IPv4 options shouldn't coalesce */ static void send_ip_options(int fd, struct sockaddr_ll *daddr) { @@ -697,7 +747,7 @@ static void send_fragment6(int fd, struct sockaddr_ll *daddr) create_packet(buf, PAYLOAD_LEN * i, 0, PAYLOAD_LEN, 0); write_packet(fd, buf, bufpkt_len, daddr); }
- sleep(1);
Leftover debug, or necessary fix to existing test?
create_packet(buf, PAYLOAD_LEN * 2, 0, PAYLOAD_LEN, 0); memset(extpkt, 0, extpkt_len); @@ -760,6 +810,7 @@ static void check_recv_pkts(int fd, int *correct_payload, vlog("}, Total %d packets\nReceived {", correct_num_pkts); while (1) {
pkt_size = recv(fd, buffer, IP_MAXPACKET + ETH_HLEN + 1, 0); if (pkt_size < 0) error(1, errno, "could not receive");ip_ext_len = 0;
@@ -767,7 +818,7 @@ static void check_recv_pkts(int fd, int *correct_payload, if (iph->version == 4) ip_ext_len = (iph->ihl - 5) * 4; else if (ip6h->version == 6 && ip6h->nexthdr != IPPROTO_TCP)
ip_ext_len = sizeof(struct ip6_frag);
ip_ext_len = MIN_EXTHDR_SIZE;
struct ip6_frag is > MIN_EXTHDR_SIZE. Need both cases?
else if (ip6h->version == 6 && ip6h->nexthdr == NEXTHDR_FRAGMENT ip_ext_len = sizeof(struct ip6_frag); else if (ip6h->version == 6 && ip6h->nexthdr != IPPROTO_TCP) ip_ext_len = MIN_EXTHDR_SIZE;
tcph = (struct tcphdr *)(buffer + tcp_offset + ip_ext_len); @@ -880,7 +931,14 @@ static void gro_sender(void) sleep(1); write_packet(txfd, fin_pkt, total_hdr_len, &daddr); } else if (proto == PF_INET6) {
sleep(1); send_fragment6(txfd, &daddr);
sleep(1);
write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
sleep(1);
send_ipv6_exthdr(txfd, &daddr);
sleep(1);
For the casual reader: these sleeps are not leftover debug statements unfortunately. The same exists in the PF_INET branch:
/* Modified packets may be received out of order. * Sleep function added to enforce test boundaries * so that fin pkts are not received prior to other pkts. */
write_packet(txfd, fin_pkt, total_hdr_len, &daddr); }
} else if (strcmp(testname, "large") == 0) { @@ -997,7 +1055,13 @@ static void gro_receiver(void) */ printf("fragmented ip6 doesn't coalesce: "); correct_payload[0] = PAYLOAD_LEN * 2;
check_recv_pkts(rxfd, correct_payload, 2);
correct_payload[1] = PAYLOAD_LEN;
correct_payload[2] = PAYLOAD_LEN;
check_recv_pkts(rxfd, correct_payload, 3);
correct_payload[0] = PAYLOAD_LEN * 2;
printf("ipv6 with extension header DOES coalesce: ");
check_recv_pkts(rxfd, correct_payload, 1);
It might be worth adding a test with two different extension headers. To verify that these should not coalesce.
Either different ipprotos, like IPPROTO_DSTOPTS vs IPPROTO_HOPOPTS. Perhaps more intesting are two of the same ipproto but with different content.
}
} else if (strcmp(testname, "large") == 0) { int offset = proto == PF_INET ? 20 : 0; -- 2.36.1