aboutsummaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--include/uapi/linux/if_packet.h2
-rw-r--r--net/packet/af_packet.c109
-rw-r--r--net/packet/internal.h3
-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
7 files changed, 451 insertions, 24 deletions
diff --git a/include/uapi/linux/if_packet.h b/include/uapi/linux/if_packet.h
index f9a60375f0d0..8136658ea477 100644
--- a/include/uapi/linux/if_packet.h
+++ b/include/uapi/linux/if_packet.h
@@ -55,6 +55,8 @@ struct sockaddr_ll {
55#define PACKET_FANOUT_HASH 0 55#define PACKET_FANOUT_HASH 0
56#define PACKET_FANOUT_LB 1 56#define PACKET_FANOUT_LB 1
57#define PACKET_FANOUT_CPU 2 57#define PACKET_FANOUT_CPU 2
58#define PACKET_FANOUT_ROLLOVER 3
59#define PACKET_FANOUT_FLAG_ROLLOVER 0x1000
58#define PACKET_FANOUT_FLAG_DEFRAG 0x8000 60#define PACKET_FANOUT_FLAG_DEFRAG 0x8000
59 61
60struct tpacket_stats { 62struct tpacket_stats {
diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
index 1d6793dbfbae..bd0d14c97d41 100644
--- a/net/packet/af_packet.c
+++ b/net/packet/af_packet.c
@@ -181,6 +181,8 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u,
181 181
182struct packet_sock; 182struct packet_sock;
183static int tpacket_snd(struct packet_sock *po, struct msghdr *msg); 183static int tpacket_snd(struct packet_sock *po, struct msghdr *msg);
184static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev,
185 struct packet_type *pt, struct net_device *orig_dev);
184 186
185static void *packet_previous_frame(struct packet_sock *po, 187static void *packet_previous_frame(struct packet_sock *po,
186 struct packet_ring_buffer *rb, 188 struct packet_ring_buffer *rb,
@@ -973,11 +975,11 @@ static void *packet_current_rx_frame(struct packet_sock *po,
973 975
974static void *prb_lookup_block(struct packet_sock *po, 976static void *prb_lookup_block(struct packet_sock *po,
975 struct packet_ring_buffer *rb, 977 struct packet_ring_buffer *rb,
976 unsigned int previous, 978 unsigned int idx,
977 int status) 979 int status)
978{ 980{
979 struct tpacket_kbdq_core *pkc = GET_PBDQC_FROM_RB(rb); 981 struct tpacket_kbdq_core *pkc = GET_PBDQC_FROM_RB(rb);
980 struct tpacket_block_desc *pbd = GET_PBLOCK_DESC(pkc, previous); 982 struct tpacket_block_desc *pbd = GET_PBLOCK_DESC(pkc, idx);
981 983
982 if (status != BLOCK_STATUS(pbd)) 984 if (status != BLOCK_STATUS(pbd))
983 return NULL; 985 return NULL;
@@ -1041,6 +1043,29 @@ static void packet_increment_head(struct packet_ring_buffer *buff)
1041 buff->head = buff->head != buff->frame_max ? buff->head+1 : 0; 1043 buff->head = buff->head != buff->frame_max ? buff->head+1 : 0;
1042} 1044}
1043 1045
1046static bool packet_rcv_has_room(struct packet_sock *po, struct sk_buff *skb)
1047{
1048 struct sock *sk = &po->sk;
1049 bool has_room;
1050
1051 if (po->prot_hook.func != tpacket_rcv)
1052 return (atomic_read(&sk->sk_rmem_alloc) + skb->truesize)
1053 <= sk->sk_rcvbuf;
1054
1055 spin_lock(&sk->sk_receive_queue.lock);
1056 if (po->tp_version == TPACKET_V3)
1057 has_room = prb_lookup_block(po, &po->rx_ring,
1058 po->rx_ring.prb_bdqc.kactive_blk_num,
1059 TP_STATUS_KERNEL);
1060 else
1061 has_room = packet_lookup_frame(po, &po->rx_ring,
1062 po->rx_ring.head,
1063 TP_STATUS_KERNEL);
1064 spin_unlock(&sk->sk_receive_queue.lock);
1065
1066 return has_room;
1067}
1068
1044static void packet_sock_destruct(struct sock *sk) 1069static void packet_sock_destruct(struct sock *sk)
1045{ 1070{
1046 skb_queue_purge(&sk->sk_error_queue); 1071 skb_queue_purge(&sk->sk_error_queue);
@@ -1066,16 +1091,16 @@ static int fanout_rr_next(struct packet_fanout *f, unsigned int num)
1066 return x; 1091 return x;
1067} 1092}
1068 1093
1069static struct sock *fanout_demux_hash(struct packet_fanout *f, struct sk_buff *skb, unsigned int num) 1094static unsigned int fanout_demux_hash(struct packet_fanout *f,
1095 struct sk_buff *skb,
1096 unsigned int num)
1070{ 1097{
1071 u32 idx, hash = skb->rxhash; 1098 return (((u64)skb->rxhash) * num) >> 32;
1072
1073 idx = ((u64)hash * num) >> 32;
1074
1075 return f->arr[idx];
1076} 1099}
1077 1100
1078static struct sock *fanout_demux_lb(struct packet_fanout *f, struct sk_buff *skb, unsigned int num) 1101static unsigned int fanout_demux_lb(struct packet_fanout *f,
1102 struct sk_buff *skb,
1103 unsigned int num)
1079{ 1104{
1080 int cur, old; 1105 int cur, old;
1081 1106
@@ -1083,14 +1108,40 @@ static struct sock *fanout_demux_lb(struct packet_fanout *f, struct sk_buff *skb
1083 while ((old = atomic_cmpxchg(&f->rr_cur, cur, 1108 while ((old = atomic_cmpxchg(&f->rr_cur, cur,
1084 fanout_rr_next(f, num))) != cur) 1109 fanout_rr_next(f, num))) != cur)
1085 cur = old; 1110 cur = old;
1086 return f->arr[cur]; 1111 return cur;
1112}
1113
1114static unsigned int fanout_demux_cpu(struct packet_fanout *f,
1115 struct sk_buff *skb,
1116 unsigned int num)
1117{
1118 return smp_processor_id() % num;
1087} 1119}
1088 1120
1089static struct sock *fanout_demux_cpu(struct packet_fanout *f, struct sk_buff *skb, unsigned int num) 1121static unsigned int fanout_demux_rollover(struct packet_fanout *f,
1122 struct sk_buff *skb,
1123 unsigned int idx, unsigned int skip,
1124 unsigned int num)
1090{ 1125{
1091 unsigned int cpu = smp_processor_id(); 1126 unsigned int i, j;
1092 1127
1093 return f->arr[cpu % num]; 1128 i = j = min_t(int, f->next[idx], num - 1);
1129 do {
1130 if (i != skip && packet_rcv_has_room(pkt_sk(f->arr[i]), skb)) {
1131 if (i != j)
1132 f->next[idx] = i;
1133 return i;
1134 }
1135 if (++i == num)
1136 i = 0;
1137 } while (i != j);
1138
1139 return idx;
1140}
1141
1142static bool fanout_has_flag(struct packet_fanout *f, u16 flag)
1143{
1144 return f->flags & (flag >> 8);
1094} 1145}
1095 1146
1096static int packet_rcv_fanout(struct sk_buff *skb, struct net_device *dev, 1147static int packet_rcv_fanout(struct sk_buff *skb, struct net_device *dev,
@@ -1099,7 +1150,7 @@ static int packet_rcv_fanout(struct sk_buff *skb, struct net_device *dev,
1099 struct packet_fanout *f = pt->af_packet_priv; 1150 struct packet_fanout *f = pt->af_packet_priv;
1100 unsigned int num = f->num_members; 1151 unsigned int num = f->num_members;
1101 struct packet_sock *po; 1152 struct packet_sock *po;
1102 struct sock *sk; 1153 unsigned int idx;
1103 1154
1104 if (!net_eq(dev_net(dev), read_pnet(&f->net)) || 1155 if (!net_eq(dev_net(dev), read_pnet(&f->net)) ||
1105 !num) { 1156 !num) {
@@ -1110,23 +1161,31 @@ static int packet_rcv_fanout(struct sk_buff *skb, struct net_device *dev,
1110 switch (f->type) { 1161 switch (f->type) {
1111 case PACKET_FANOUT_HASH: 1162 case PACKET_FANOUT_HASH:
1112 default: 1163 default:
1113 if (f->defrag) { 1164 if (fanout_has_flag(f, PACKET_FANOUT_FLAG_DEFRAG)) {
1114 skb = ip_check_defrag(skb, IP_DEFRAG_AF_PACKET); 1165 skb = ip_check_defrag(skb, IP_DEFRAG_AF_PACKET);
1115 if (!skb) 1166 if (!skb)
1116 return 0; 1167 return 0;
1117 } 1168 }
1118 skb_get_rxhash(skb); 1169 skb_get_rxhash(skb);
1119 sk = fanout_demux_hash(f, skb, num); 1170 idx = fanout_demux_hash(f, skb, num);
1120 break; 1171 break;
1121 case PACKET_FANOUT_LB: 1172 case PACKET_FANOUT_LB:
1122 sk = fanout_demux_lb(f, skb, num); 1173 idx = fanout_demux_lb(f, skb, num);
1123 break; 1174 break;
1124 case PACKET_FANOUT_CPU: 1175 case PACKET_FANOUT_CPU:
1125 sk = fanout_demux_cpu(f, skb, num); 1176 idx = fanout_demux_cpu(f, skb, num);
1177 break;
1178 case PACKET_FANOUT_ROLLOVER:
1179 idx = fanout_demux_rollover(f, skb, 0, (unsigned int) -1, num);
1126 break; 1180 break;
1127 } 1181 }
1128 1182
1129 po = pkt_sk(sk); 1183 po = pkt_sk(f->arr[idx]);
1184 if (fanout_has_flag(f, PACKET_FANOUT_FLAG_ROLLOVER) &&
1185 unlikely(!packet_rcv_has_room(po, skb))) {
1186 idx = fanout_demux_rollover(f, skb, idx, idx, num);
1187 po = pkt_sk(f->arr[idx]);
1188 }
1130 1189
1131 return po->prot_hook.func(skb, dev, &po->prot_hook, orig_dev); 1190 return po->prot_hook.func(skb, dev, &po->prot_hook, orig_dev);
1132} 1191}
@@ -1175,10 +1234,13 @@ static int fanout_add(struct sock *sk, u16 id, u16 type_flags)
1175 struct packet_sock *po = pkt_sk(sk); 1234 struct packet_sock *po = pkt_sk(sk);
1176 struct packet_fanout *f, *match; 1235 struct packet_fanout *f, *match;
1177 u8 type = type_flags & 0xff; 1236 u8 type = type_flags & 0xff;
1178 u8 defrag = (type_flags & PACKET_FANOUT_FLAG_DEFRAG) ? 1 : 0; 1237 u8 flags = type_flags >> 8;
1179 int err; 1238 int err;
1180 1239
1181 switch (type) { 1240 switch (type) {
1241 case PACKET_FANOUT_ROLLOVER:
1242 if (type_flags & PACKET_FANOUT_FLAG_ROLLOVER)
1243 return -EINVAL;
1182 case PACKET_FANOUT_HASH: 1244 case PACKET_FANOUT_HASH:
1183 case PACKET_FANOUT_LB: 1245 case PACKET_FANOUT_LB:
1184 case PACKET_FANOUT_CPU: 1246 case PACKET_FANOUT_CPU:
@@ -1203,7 +1265,7 @@ static int fanout_add(struct sock *sk, u16 id, u16 type_flags)
1203 } 1265 }
1204 } 1266 }
1205 err = -EINVAL; 1267 err = -EINVAL;
1206 if (match && match->defrag != defrag) 1268 if (match && match->flags != flags)
1207 goto out; 1269 goto out;
1208 if (!match) { 1270 if (!match) {
1209 err = -ENOMEM; 1271 err = -ENOMEM;
@@ -1213,7 +1275,7 @@ static int fanout_add(struct sock *sk, u16 id, u16 type_flags)
1213 write_pnet(&match->net, sock_net(sk)); 1275 write_pnet(&match->net, sock_net(sk));
1214 match->id = id; 1276 match->id = id;
1215 match->type = type; 1277 match->type = type;
1216 match->defrag = defrag; 1278 match->flags = flags;
1217 atomic_set(&match->rr_cur, 0); 1279 atomic_set(&match->rr_cur, 0);
1218 INIT_LIST_HEAD(&match->list); 1280 INIT_LIST_HEAD(&match->list);
1219 spin_lock_init(&match->lock); 1281 spin_lock_init(&match->lock);
@@ -3240,7 +3302,8 @@ static int packet_getsockopt(struct socket *sock, int level, int optname,
3240 case PACKET_FANOUT: 3302 case PACKET_FANOUT:
3241 val = (po->fanout ? 3303 val = (po->fanout ?
3242 ((u32)po->fanout->id | 3304 ((u32)po->fanout->id |
3243 ((u32)po->fanout->type << 16)) : 3305 ((u32)po->fanout->type << 16) |
3306 ((u32)po->fanout->flags << 24)) :
3244 0); 3307 0);
3245 break; 3308 break;
3246 case PACKET_TX_HAS_OFF: 3309 case PACKET_TX_HAS_OFF:
diff --git a/net/packet/internal.h b/net/packet/internal.h
index e84cab8cb7a9..e891f025a1b9 100644
--- a/net/packet/internal.h
+++ b/net/packet/internal.h
@@ -77,10 +77,11 @@ struct packet_fanout {
77 unsigned int num_members; 77 unsigned int num_members;
78 u16 id; 78 u16 id;
79 u8 type; 79 u8 type;
80 u8 defrag; 80 u8 flags;
81 atomic_t rr_cur; 81 atomic_t rr_cur;
82 struct list_head list; 82 struct list_head list;
83 struct sock *arr[PACKET_FANOUT_MAX]; 83 struct sock *arr[PACKET_FANOUT_MAX];
84 int next[PACKET_FANOUT_MAX];
84 spinlock_t lock; 85 spinlock_t lock;
85 atomic_t sk_ref; 86 atomic_t sk_ref;
86 struct packet_type prot_hook ____cacheline_aligned_in_smp; 87 struct packet_type prot_hook ____cacheline_aligned_in_smp;
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