1 // SPDX-License-Identifier: GPL-2.0
8 #include <netinet/if_ether.h>
9 #include <netinet/in.h>
10 #include <netinet/ip.h>
11 #include <netinet/ip6.h>
12 #include <netinet/udp.h>
20 #include <sys/socket.h>
22 #include <sys/types.h>
26 #define ETH_MAX_MTU 0xFFFFU
30 #define UDP_SEGMENT 103
34 #define SO_ZEROCOPY 60
38 #define MSG_ZEROCOPY 0x4000000
43 static bool cfg_cache_trash
;
44 static int cfg_cpu
= -1;
45 static int cfg_connected
= true;
46 static int cfg_family
= PF_UNSPEC
;
47 static uint16_t cfg_mss
;
48 static int cfg_payload_len
= (1472 * 42);
49 static int cfg_port
= 8000;
50 static int cfg_runtime_ms
= -1;
51 static bool cfg_segment
;
52 static bool cfg_sendmmsg
;
54 static bool cfg_zerocopy
;
56 static socklen_t cfg_alen
;
57 static struct sockaddr_storage cfg_dst_addr
;
59 static bool interrupted
;
60 static char buf
[NUM_PKT
][ETH_MAX_MTU
];
62 static void sigint_handler(int signum
)
68 static unsigned long gettimeofday_ms(void)
72 gettimeofday(&tv
, NULL
);
73 return (tv
.tv_sec
* 1000) + (tv
.tv_usec
/ 1000);
76 static int set_cpu(int cpu
)
82 if (sched_setaffinity(0, sizeof(mask
), &mask
))
83 error(1, 0, "setaffinity %d", cpu
);
88 static void setup_sockaddr(int domain
, const char *str_addr
, void *sockaddr
)
90 struct sockaddr_in6
*addr6
= (void *) sockaddr
;
91 struct sockaddr_in
*addr4
= (void *) sockaddr
;
95 addr4
->sin_family
= AF_INET
;
96 addr4
->sin_port
= htons(cfg_port
);
97 if (inet_pton(AF_INET
, str_addr
, &(addr4
->sin_addr
)) != 1)
98 error(1, 0, "ipv4 parse error: %s", str_addr
);
101 addr6
->sin6_family
= AF_INET6
;
102 addr6
->sin6_port
= htons(cfg_port
);
103 if (inet_pton(AF_INET6
, str_addr
, &(addr6
->sin6_addr
)) != 1)
104 error(1, 0, "ipv6 parse error: %s", str_addr
);
107 error(1, 0, "illegal domain");
111 static void flush_zerocopy(int fd
)
113 struct msghdr msg
= {0}; /* flush */
117 ret
= recvmsg(fd
, &msg
, MSG_ERRQUEUE
);
118 if (ret
== -1 && errno
== EAGAIN
)
121 error(1, errno
, "errqueue");
122 if (msg
.msg_flags
!= (MSG_ERRQUEUE
| MSG_CTRUNC
))
123 error(1, 0, "errqueue: flags 0x%x\n", msg
.msg_flags
);
128 static int send_tcp(int fd
, char *data
)
130 int ret
, done
= 0, count
= 0;
132 while (done
< cfg_payload_len
) {
133 ret
= send(fd
, data
+ done
, cfg_payload_len
- done
,
134 cfg_zerocopy
? MSG_ZEROCOPY
: 0);
136 error(1, errno
, "write");
145 static int send_udp(int fd
, char *data
)
147 int ret
, total_len
, len
, count
= 0;
149 total_len
= cfg_payload_len
;
152 len
= total_len
< cfg_mss
? total_len
: cfg_mss
;
154 ret
= sendto(fd
, data
, len
, cfg_zerocopy
? MSG_ZEROCOPY
: 0,
155 cfg_connected
? NULL
: (void *)&cfg_dst_addr
,
156 cfg_connected
? 0 : cfg_alen
);
158 error(1, errno
, "write");
160 error(1, errno
, "write: %uB != %uB\n", ret
, len
);
169 static int send_udp_sendmmsg(int fd
, char *data
)
171 const int max_nr_msg
= ETH_MAX_MTU
/ ETH_DATA_LEN
;
172 struct mmsghdr mmsgs
[max_nr_msg
];
173 struct iovec iov
[max_nr_msg
];
174 unsigned int off
= 0, left
;
177 memset(mmsgs
, 0, sizeof(mmsgs
));
179 left
= cfg_payload_len
;
182 error(1, 0, "sendmmsg: exceeds max_nr_msg");
184 iov
[i
].iov_base
= data
+ off
;
185 iov
[i
].iov_len
= cfg_mss
< left
? cfg_mss
: left
;
187 mmsgs
[i
].msg_hdr
.msg_iov
= iov
+ i
;
188 mmsgs
[i
].msg_hdr
.msg_iovlen
= 1;
190 off
+= iov
[i
].iov_len
;
191 left
-= iov
[i
].iov_len
;
195 ret
= sendmmsg(fd
, mmsgs
, i
, cfg_zerocopy
? MSG_ZEROCOPY
: 0);
197 error(1, errno
, "sendmmsg");
202 static void send_udp_segment_cmsg(struct cmsghdr
*cm
)
206 cm
->cmsg_level
= SOL_UDP
;
207 cm
->cmsg_type
= UDP_SEGMENT
;
208 cm
->cmsg_len
= CMSG_LEN(sizeof(cfg_mss
));
209 valp
= (void *)CMSG_DATA(cm
);
213 static int send_udp_segment(int fd
, char *data
)
215 char control
[CMSG_SPACE(sizeof(cfg_mss
))] = {0};
216 struct msghdr msg
= {0};
217 struct iovec iov
= {0};
221 iov
.iov_len
= cfg_payload_len
;
226 msg
.msg_control
= control
;
227 msg
.msg_controllen
= sizeof(control
);
228 send_udp_segment_cmsg(CMSG_FIRSTHDR(&msg
));
230 msg
.msg_name
= (void *)&cfg_dst_addr
;
231 msg
.msg_namelen
= cfg_alen
;
233 ret
= sendmsg(fd
, &msg
, cfg_zerocopy
? MSG_ZEROCOPY
: 0);
235 error(1, errno
, "sendmsg");
236 if (ret
!= iov
.iov_len
)
237 error(1, 0, "sendmsg: %u != %lu\n", ret
, iov
.iov_len
);
242 static void usage(const char *filepath
)
244 error(1, 0, "Usage: %s [-46cmStuz] [-C cpu] [-D dst ip] [-l secs] [-p port] [-s sendsize]",
248 static void parse_opts(int argc
, char **argv
)
253 while ((c
= getopt(argc
, argv
, "46cC:D:l:mp:s:Stuz")) != -1) {
256 if (cfg_family
!= PF_UNSPEC
)
257 error(1, 0, "Pass one of -4 or -6");
258 cfg_family
= PF_INET
;
259 cfg_alen
= sizeof(struct sockaddr_in
);
262 if (cfg_family
!= PF_UNSPEC
)
263 error(1, 0, "Pass one of -4 or -6");
264 cfg_family
= PF_INET6
;
265 cfg_alen
= sizeof(struct sockaddr_in6
);
268 cfg_cache_trash
= true;
271 cfg_cpu
= strtol(optarg
, NULL
, 0);
274 setup_sockaddr(cfg_family
, optarg
, &cfg_dst_addr
);
277 cfg_runtime_ms
= strtoul(optarg
, NULL
, 10) * 1000;
283 cfg_port
= strtoul(optarg
, NULL
, 0);
286 cfg_payload_len
= strtoul(optarg
, NULL
, 0);
295 cfg_connected
= false;
306 if (cfg_family
== PF_UNSPEC
)
307 error(1, 0, "must pass one of -4 or -6");
308 if (cfg_tcp
&& !cfg_connected
)
309 error(1, 0, "connectionless tcp makes no sense");
310 if (cfg_segment
&& cfg_sendmmsg
)
311 error(1, 0, "cannot combine segment offload and sendmmsg");
313 if (cfg_family
== PF_INET
)
314 hdrlen
= sizeof(struct iphdr
) + sizeof(struct udphdr
);
316 hdrlen
= sizeof(struct ip6_hdr
) + sizeof(struct udphdr
);
318 cfg_mss
= ETH_DATA_LEN
- hdrlen
;
319 max_len
= ETH_MAX_MTU
- hdrlen
;
321 if (cfg_payload_len
> max_len
)
322 error(1, 0, "payload length %u exceeds max %u",
323 cfg_payload_len
, max_len
);
326 static void set_pmtu_discover(int fd
, bool is_ipv4
)
328 int level
, name
, val
;
332 name
= IP_MTU_DISCOVER
;
333 val
= IP_PMTUDISC_DO
;
336 name
= IPV6_MTU_DISCOVER
;
337 val
= IPV6_PMTUDISC_DO
;
340 if (setsockopt(fd
, level
, name
, &val
, sizeof(val
)))
341 error(1, errno
, "setsockopt path mtu");
344 int main(int argc
, char **argv
)
346 unsigned long num_msgs
, num_sends
;
347 unsigned long tnow
, treport
, tstop
;
350 parse_opts(argc
, argv
);
355 for (i
= 0; i
< sizeof(buf
[0]); i
++)
356 buf
[0][i
] = 'a' + (i
% 26);
357 for (i
= 1; i
< NUM_PKT
; i
++)
358 memcpy(buf
[i
], buf
[0], sizeof(buf
[0]));
360 signal(SIGINT
, sigint_handler
);
362 fd
= socket(cfg_family
, cfg_tcp
? SOCK_STREAM
: SOCK_DGRAM
, 0);
364 error(1, errno
, "socket");
368 if (setsockopt(fd
, SOL_SOCKET
, SO_ZEROCOPY
, &val
, sizeof(val
)))
369 error(1, errno
, "setsockopt zerocopy");
373 connect(fd
, (void *)&cfg_dst_addr
, cfg_alen
))
374 error(1, errno
, "connect");
377 set_pmtu_discover(fd
, cfg_family
== PF_INET
);
379 num_msgs
= num_sends
= 0;
380 tnow
= gettimeofday_ms();
381 tstop
= tnow
+ cfg_runtime_ms
;
382 treport
= tnow
+ 1000;
387 num_sends
+= send_tcp(fd
, buf
[i
]);
388 else if (cfg_segment
)
389 num_sends
+= send_udp_segment(fd
, buf
[i
]);
390 else if (cfg_sendmmsg
)
391 num_sends
+= send_udp_sendmmsg(fd
, buf
[i
]);
393 num_sends
+= send_udp(fd
, buf
[i
]);
396 if (cfg_zerocopy
&& ((num_msgs
& 0xF) == 0))
399 tnow
= gettimeofday_ms();
400 if (tnow
> treport
) {
402 "%s tx: %6lu MB/s %8lu calls/s %6lu msg/s\n",
403 cfg_tcp
? "tcp" : "udp",
404 (num_msgs
* cfg_payload_len
) >> 20,
405 num_sends
, num_msgs
);
406 num_msgs
= num_sends
= 0;
407 treport
= tnow
+ 1000;
410 /* cold cache when writing buffer */
412 i
= ++i
< NUM_PKT
? i
: 0;
414 } while (!interrupted
&& (cfg_runtime_ms
== -1 || tnow
< tstop
));
417 error(1, errno
, "close");