aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorWillem de Bruijn <willemb@google.com>2013-03-19 06:18:11 -0400
committerDavid S. Miller <davem@davemloft.net>2013-03-19 17:15:04 -0400
commit77f65ebdca506870d99bfabe52bde222511022ec (patch)
tree8f5ba6c76d1b49b44128d08281cc0b6f3e62285c /tools
parentb0aa73bf081da6810dacd750b9f8186640e172db (diff)
packet: packet fanout rollover during socket overload
Changes: v3->v2: rebase (no other changes) passes selftest v2->v1: read f->num_members only once fix bug: test rollover mode + flag Minimize packet drop in a fanout group. If one socket is full, roll over packets to another from the group. Maintain flow affinity during normal load using an rxhash fanout policy, while dispersing unexpected traffic storms that hit a single cpu, such as spoofed-source DoS flows. Rollover breaks affinity for flows arriving at saturated sockets during those conditions. The patch adds a fanout policy ROLLOVER that rotates between sockets, filling each socket before moving to the next. It also adds a fanout flag ROLLOVER. If passed along with any other fanout policy, the primary policy is applied until the chosen socket is full. Then, rollover selects another socket, to delay packet drop until the entire system is saturated. Probing sockets is not free. Selecting the last used socket, as rollover does, is a greedy approach that maximizes chance of success, at the cost of extreme load imbalance. In practice, with sufficiently long queues to absorb bursts, sockets are drained in parallel and load balance looks uniform in `top`. To avoid contention, scales counters with number of sockets and accesses them lockfree. Values are bounds checked to ensure correctness. Tested using an application with 9 threads pinned to CPUs, one socket per thread and sufficient busywork per packet operation to limits each thread to handling 32 Kpps. When sent 500 Kpps single UDP stream packets, a FANOUT_CPU setup processes 32 Kpps in total without this patch, 270 Kpps with the patch. Tested with read() and with a packet ring (V1). Also, passes psock_fanout.c unit test added to selftests. Signed-off-by: Willem de Bruijn <willemb@google.com> Reviewed-by: Eric Dumazet <edumazet@google.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'tools')
-rw-r--r--tools/testing/selftests/Makefile1
-rw-r--r--tools/testing/selftests/net-afpacket/Makefile18
-rw-r--r--tools/testing/selftests/net-afpacket/psock_fanout.c326
-rw-r--r--tools/testing/selftests/net-afpacket/run_afpackettests16
4 files changed, 361 insertions, 0 deletions
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 7c6280f1cf4d..7f50078d0e8c 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -6,6 +6,7 @@ TARGETS += cpu-hotplug
6TARGETS += memory-hotplug 6TARGETS += memory-hotplug
7TARGETS += efivarfs 7TARGETS += efivarfs
8TARGETS += net-socket 8TARGETS += net-socket
9TARGETS += net-afpacket
9 10
10all: 11all:
11 for TARGET in $(TARGETS); do \ 12 for TARGET in $(TARGETS); do \
diff --git a/tools/testing/selftests/net-afpacket/Makefile b/tools/testing/selftests/net-afpacket/Makefile
new file mode 100644
index 000000000000..45f2ffb7fda7
--- /dev/null
+++ b/tools/testing/selftests/net-afpacket/Makefile
@@ -0,0 +1,18 @@
1# Makefile for net-socket selftests
2
3CC = $(CROSS_COMPILE)gcc
4CFLAGS = -Wall
5
6CFLAGS += -I../../../../usr/include/
7
8AF_PACKET_PROGS = psock_fanout
9
10all: $(AF_PACKET_PROGS)
11%: %.c
12 $(CC) $(CFLAGS) -o $@ $^
13
14run_tests: all
15 @/bin/sh ./run_afpackettests || echo "afpackettests: [FAIL]"
16
17clean:
18 $(RM) $(AF_PACKET_PROGS)
diff --git a/tools/testing/selftests/net-afpacket/psock_fanout.c b/tools/testing/selftests/net-afpacket/psock_fanout.c
new file mode 100644
index 000000000000..09dbf93c53d4
--- /dev/null
+++ b/tools/testing/selftests/net-afpacket/psock_fanout.c
@@ -0,0 +1,326 @@
1/*
2 * Copyright 2013 Google Inc.
3 * Author: Willem de Bruijn (willemb@google.com)
4 *
5 * A basic test of packet socket fanout behavior.
6 *
7 * Control:
8 * - create fanout fails as expected with illegal flag combinations
9 * - join fanout fails as expected with diverging types or flags
10 *
11 * Datapath:
12 * Open a pair of packet sockets and a pair of INET sockets, send a known
13 * number of packets across the two INET sockets and count the number of
14 * packets enqueued onto the two packet sockets.
15 *
16 * The test currently runs for
17 * - PACKET_FANOUT_HASH
18 * - PACKET_FANOUT_HASH with PACKET_FANOUT_FLAG_ROLLOVER
19 * - PACKET_FANOUT_ROLLOVER
20 *
21 * Todo:
22 * - datapath: PACKET_FANOUT_LB
23 * - datapath: PACKET_FANOUT_CPU
24 * - functionality: PACKET_FANOUT_FLAG_DEFRAG
25 *
26 * License (GPLv2):
27 *
28 * This program is free software; you can redistribute it and/or modify it
29 * under the terms and conditions of the GNU General Public License,
30 * version 2, as published by the Free Software Foundation.
31 *
32 * This program is distributed in the hope it will be useful, but WITHOUT
33 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
34 * FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for
35 * more details.
36 *
37 * You should have received a copy of the GNU General Public License along with
38 * this program; if not, write to the Free Software Foundation, Inc.,
39 * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
40 */
41
42#include <arpa/inet.h>
43#include <errno.h>
44#include <linux/filter.h>
45#include <linux/if_packet.h>
46#include <net/ethernet.h>
47#include <netinet/ip.h>
48#include <netinet/udp.h>
49#include <fcntl.h>
50#include <stdint.h>
51#include <stdio.h>
52#include <stdlib.h>
53#include <string.h>
54#include <sys/socket.h>
55#include <sys/stat.h>
56#include <sys/types.h>
57#include <unistd.h>
58
59/* Hack: build even if local includes are old */
60#ifndef PACKET_FANOUT
61#define PACKET_FANOUT 18
62#define PACKET_FANOUT_HASH 0
63#define PACKET_FANOUT_LB 1
64#define PACKET_FANOUT_CPU 2
65#define PACKET_FANOUT_FLAG_DEFRAG 0x8000
66
67#ifndef PACKET_FANOUT_ROLLOVER
68#define PACKET_FANOUT_ROLLOVER 3
69#endif
70
71#ifndef PACKET_FANOUT_FLAG_ROLLOVER
72#define PACKET_FANOUT_FLAG_ROLLOVER 0x1000
73#endif
74
75#endif
76
77#define DATA_LEN 100
78#define DATA_CHAR 'a'
79
80static void pair_udp_open(int fds[], uint16_t port)
81{
82 struct sockaddr_in saddr, daddr;
83
84 fds[0] = socket(PF_INET, SOCK_DGRAM, 0);
85 fds[1] = socket(PF_INET, SOCK_DGRAM, 0);
86 if (fds[0] == -1 || fds[1] == -1) {
87 fprintf(stderr, "ERROR: socket dgram\n");
88 exit(1);
89 }
90
91 memset(&saddr, 0, sizeof(saddr));
92 saddr.sin_family = AF_INET;
93 saddr.sin_port = htons(port);
94 saddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
95
96 memset(&daddr, 0, sizeof(daddr));
97 daddr.sin_family = AF_INET;
98 daddr.sin_port = htons(port + 1);
99 daddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
100
101 /* must bind both to get consistent hash result */
102 if (bind(fds[1], (void *) &daddr, sizeof(daddr))) {
103 perror("bind");
104 exit(1);
105 }
106 if (bind(fds[0], (void *) &saddr, sizeof(saddr))) {
107 perror("bind");
108 exit(1);
109 }
110 if (connect(fds[0], (void *) &daddr, sizeof(daddr))) {
111 perror("bind");
112 exit(1);
113 }
114}
115
116static void pair_udp_send(int fds[], int num)
117{
118 char buf[DATA_LEN], rbuf[DATA_LEN];
119
120 memset(buf, DATA_CHAR, sizeof(buf));
121 while (num--) {
122 /* Should really handle EINTR and EAGAIN */
123 if (write(fds[0], buf, sizeof(buf)) != sizeof(buf)) {
124 fprintf(stderr, "ERROR: send failed left=%d\n", num);
125 exit(1);
126 }
127 if (read(fds[1], rbuf, sizeof(rbuf)) != sizeof(rbuf)) {
128 fprintf(stderr, "ERROR: recv failed left=%d\n", num);
129 exit(1);
130 }
131 if (memcmp(buf, rbuf, sizeof(buf))) {
132 fprintf(stderr, "ERROR: data failed left=%d\n", num);
133 exit(1);
134 }
135 }
136}
137
138static void sock_fanout_setfilter(int fd)
139{
140 struct sock_filter bpf_filter[] = {
141 { 0x80, 0, 0, 0x00000000 }, /* LD pktlen */
142 { 0x35, 0, 5, DATA_LEN }, /* JGE DATA_LEN [f goto nomatch]*/
143 { 0x30, 0, 0, 0x00000050 }, /* LD ip[80] */
144 { 0x15, 0, 3, DATA_CHAR }, /* JEQ DATA_CHAR [f goto nomatch]*/
145 { 0x30, 0, 0, 0x00000051 }, /* LD ip[81] */
146 { 0x15, 0, 1, DATA_CHAR }, /* JEQ DATA_CHAR [f goto nomatch]*/
147 { 0x6, 0, 0, 0x00000060 }, /* RET match */
148/* nomatch */ { 0x6, 0, 0, 0x00000000 }, /* RET no match */
149 };
150 struct sock_fprog bpf_prog;
151
152 bpf_prog.filter = bpf_filter;
153 bpf_prog.len = sizeof(bpf_filter) / sizeof(struct sock_filter);
154 if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf_prog,
155 sizeof(bpf_prog))) {
156 perror("setsockopt SO_ATTACH_FILTER");
157 exit(1);
158 }
159}
160
161/* Open a socket in a given fanout mode.
162 * @return -1 if mode is bad, a valid socket otherwise */
163static int sock_fanout_open(uint16_t typeflags, int num_packets)
164{
165 int fd, val;
166
167 fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
168 if (fd < 0) {
169 perror("socket packet");
170 exit(1);
171 }
172
173 /* fanout group ID is always 0: tests whether old groups are deleted */
174 val = ((int) typeflags) << 16;
175 if (setsockopt(fd, SOL_PACKET, PACKET_FANOUT, &val, sizeof(val))) {
176 if (close(fd)) {
177 perror("close packet");
178 exit(1);
179 }
180 return -1;
181 }
182
183 val = sizeof(struct iphdr) + sizeof(struct udphdr) + DATA_LEN;
184 val *= num_packets;
185 /* hack: apparently, the above calculation is too small (TODO: fix) */
186 val *= 3;
187 if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val))) {
188 perror("setsockopt SO_RCVBUF");
189 exit(1);
190 }
191
192 sock_fanout_setfilter(fd);
193 return fd;
194}
195
196static void sock_fanout_read(int fds[], const int expect[])
197{
198 struct tpacket_stats stats;
199 socklen_t ssize;
200 int ret[2];
201
202 ssize = sizeof(stats);
203 if (getsockopt(fds[0], SOL_PACKET, PACKET_STATISTICS, &stats, &ssize)) {
204 perror("getsockopt statistics 0");
205 exit(1);
206 }
207 ret[0] = stats.tp_packets - stats.tp_drops;
208 ssize = sizeof(stats);
209 if (getsockopt(fds[1], SOL_PACKET, PACKET_STATISTICS, &stats, &ssize)) {
210 perror("getsockopt statistics 1");
211 exit(1);
212 }
213 ret[1] = stats.tp_packets - stats.tp_drops;
214
215 fprintf(stderr, "info: count=%d,%d, expect=%d,%d\n",
216 ret[0], ret[1], expect[0], expect[1]);
217
218 if ((!(ret[0] == expect[0] && ret[1] == expect[1])) &&
219 (!(ret[0] == expect[1] && ret[1] == expect[0]))) {
220 fprintf(stderr, "ERROR: incorrect queue lengths\n");
221 exit(1);
222 }
223}
224
225/* Test illegal mode + flag combination */
226static void test_control_single(void)
227{
228 fprintf(stderr, "test: control single socket\n");
229
230 if (sock_fanout_open(PACKET_FANOUT_ROLLOVER |
231 PACKET_FANOUT_FLAG_ROLLOVER, 0) != -1) {
232 fprintf(stderr, "ERROR: opened socket with dual rollover\n");
233 exit(1);
234 }
235}
236
237/* Test illegal group with different modes or flags */
238static void test_control_group(void)
239{
240 int fds[2];
241
242 fprintf(stderr, "test: control multiple sockets\n");
243
244 fds[0] = sock_fanout_open(PACKET_FANOUT_HASH, 20);
245 if (fds[0] == -1) {
246 fprintf(stderr, "ERROR: failed to open HASH socket\n");
247 exit(1);
248 }
249 if (sock_fanout_open(PACKET_FANOUT_HASH |
250 PACKET_FANOUT_FLAG_DEFRAG, 10) != -1) {
251 fprintf(stderr, "ERROR: joined group with wrong flag defrag\n");
252 exit(1);
253 }
254 if (sock_fanout_open(PACKET_FANOUT_HASH |
255 PACKET_FANOUT_FLAG_ROLLOVER, 10) != -1) {
256 fprintf(stderr, "ERROR: joined group with wrong flag ro\n");
257 exit(1);
258 }
259 if (sock_fanout_open(PACKET_FANOUT_CPU, 10) != -1) {
260 fprintf(stderr, "ERROR: joined group with wrong mode\n");
261 exit(1);
262 }
263 fds[1] = sock_fanout_open(PACKET_FANOUT_HASH, 20);
264 if (fds[1] == -1) {
265 fprintf(stderr, "ERROR: failed to join group\n");
266 exit(1);
267 }
268 if (close(fds[1]) || close(fds[0])) {
269 fprintf(stderr, "ERROR: closing sockets\n");
270 exit(1);
271 }
272}
273
274static void test_datapath(uint16_t typeflags,
275 const int expect1[], const int expect2[])
276{
277 const int expect0[] = { 0, 0 };
278 int fds[2], fds_udp[2][2];
279
280 fprintf(stderr, "test: datapath 0x%hx\n", typeflags);
281
282 fds[0] = sock_fanout_open(typeflags, 20);
283 fds[1] = sock_fanout_open(typeflags, 20);
284 if (fds[0] == -1 || fds[1] == -1) {
285 fprintf(stderr, "ERROR: failed open\n");
286 exit(1);
287 }
288 pair_udp_open(fds_udp[0], 8000);
289 pair_udp_open(fds_udp[1], 8002);
290 sock_fanout_read(fds, expect0);
291
292 /* Send data, but not enough to overflow a queue */
293 pair_udp_send(fds_udp[0], 15);
294 pair_udp_send(fds_udp[1], 5);
295 sock_fanout_read(fds, expect1);
296
297 /* Send more data, overflow the queue */
298 pair_udp_send(fds_udp[0], 15);
299 /* TODO: ensure consistent order between expect1 and expect2 */
300 sock_fanout_read(fds, expect2);
301
302 if (close(fds_udp[1][1]) || close(fds_udp[1][0]) ||
303 close(fds_udp[0][1]) || close(fds_udp[0][0]) ||
304 close(fds[1]) || close(fds[0])) {
305 fprintf(stderr, "close datapath\n");
306 exit(1);
307 }
308}
309
310int main(int argc, char **argv)
311{
312 const int expect_hash[2][2] = { { 15, 5 }, { 5, 0 } };
313 const int expect_hash_rb[2][2] = { { 15, 5 }, { 5, 10 } };
314 const int expect_rb[2][2] = { { 20, 0 }, { 0, 15 } };
315
316 test_control_single();
317 test_control_group();
318
319 test_datapath(PACKET_FANOUT_HASH, expect_hash[0], expect_hash[1]);
320 test_datapath(PACKET_FANOUT_HASH | PACKET_FANOUT_FLAG_ROLLOVER,
321 expect_hash_rb[0], expect_hash_rb[1]);
322 test_datapath(PACKET_FANOUT_ROLLOVER, expect_rb[0], expect_rb[1]);
323
324 printf("OK. All tests passed\n");
325 return 0;
326}
diff --git a/tools/testing/selftests/net-afpacket/run_afpackettests b/tools/testing/selftests/net-afpacket/run_afpackettests
new file mode 100644
index 000000000000..7907824c6355
--- /dev/null
+++ b/tools/testing/selftests/net-afpacket/run_afpackettests
@@ -0,0 +1,16 @@
1#!/bin/sh
2
3if [ $(id -u) != 0 ]; then
4 echo $msg must be run as root >&2
5 exit 0
6fi
7
8echo "--------------------"
9echo "running psock_fanout test"
10echo "--------------------"
11./psock_fanout
12if [ $? -ne 0 ]; then
13 echo "[FAIL]"
14else
15 echo "[PASS]"
16fi