1 |
|
|
/* |
2 |
|
|
* Copyright (c) 2008 Damien Miller <djm@mindrot.org> |
3 |
|
|
* Copyright (c) 2011 Christiano F. Haesbaert <haesbaert@haesbaert.org> |
4 |
|
|
* |
5 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
6 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
7 |
|
|
* copyright notice and this permission notice appear in all copies. |
8 |
|
|
* |
9 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
10 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
11 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
12 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
13 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
14 |
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
15 |
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 |
|
|
*/ |
17 |
|
|
|
18 |
|
|
#include <sys/types.h> |
19 |
|
|
#include <sys/time.h> |
20 |
|
|
#include <sys/socket.h> |
21 |
|
|
#include <sys/socketvar.h> |
22 |
|
|
#include <sys/resource.h> |
23 |
|
|
#include <sys/queue.h> |
24 |
|
|
#include <sys/un.h> |
25 |
|
|
|
26 |
|
|
#include <net/route.h> |
27 |
|
|
|
28 |
|
|
#include <netinet/in.h> |
29 |
|
|
#include <netinet/ip.h> |
30 |
|
|
#include <netinet/tcp.h> |
31 |
|
|
#include <netinet/tcp_timer.h> |
32 |
|
|
#include <netinet/tcp_fsm.h> |
33 |
|
|
#include <netinet/in_pcb.h> |
34 |
|
|
#include <netinet/tcp_var.h> |
35 |
|
|
|
36 |
|
|
#include <arpa/inet.h> |
37 |
|
|
|
38 |
|
|
#include <unistd.h> |
39 |
|
|
#include <limits.h> |
40 |
|
|
#include <stdlib.h> |
41 |
|
|
#include <stdio.h> |
42 |
|
|
#include <string.h> |
43 |
|
|
#include <errno.h> |
44 |
|
|
#include <event.h> |
45 |
|
|
#include <netdb.h> |
46 |
|
|
#include <signal.h> |
47 |
|
|
#include <err.h> |
48 |
|
|
#include <fcntl.h> |
49 |
|
|
#include <poll.h> |
50 |
|
|
|
51 |
|
|
#include <kvm.h> |
52 |
|
|
#include <nlist.h> |
53 |
|
|
|
54 |
|
|
#define DEFAULT_PORT "12345" |
55 |
|
|
#define DEFAULT_STATS_INTERVAL 1000 /* ms */ |
56 |
|
|
#define DEFAULT_BUF (256 * 1024) |
57 |
|
|
#define DEFAULT_UDP_PKT (1500 - 28) /* TODO don't hardcode this */ |
58 |
|
|
#define TCP_MODE !ptb->uflag |
59 |
|
|
#define UDP_MODE ptb->uflag |
60 |
|
|
#define MAX_FD 1024 |
61 |
|
|
|
62 |
|
|
/* Our tcpbench globals */ |
63 |
|
|
struct { |
64 |
|
|
int Sflag; /* Socket buffer size (tcp mode) */ |
65 |
|
|
u_int rflag; /* Report rate (ms) */ |
66 |
|
|
int sflag; /* True if server */ |
67 |
|
|
int Tflag; /* ToS if != -1 */ |
68 |
|
|
int vflag; /* Verbose */ |
69 |
|
|
int uflag; /* UDP mode */ |
70 |
|
|
int Uflag; /* UNIX (AF_LOCAL) mode */ |
71 |
|
|
int Rflag; /* randomize client write size */ |
72 |
|
|
kvm_t *kvmh; /* Kvm handler */ |
73 |
|
|
char **kvars; /* Kvm enabled vars */ |
74 |
|
|
u_long ktcbtab; /* Ktcb */ |
75 |
|
|
char *dummybuf; /* IO buffer */ |
76 |
|
|
size_t dummybuf_len; /* IO buffer len */ |
77 |
|
|
} tcpbench, *ptb; |
78 |
|
|
|
79 |
|
|
struct tcpservsock { |
80 |
|
|
struct event ev; |
81 |
|
|
struct event evt; |
82 |
|
|
int fd; |
83 |
|
|
}; |
84 |
|
|
|
85 |
|
|
/* stats for a single tcp connection, udp uses only one */ |
86 |
|
|
struct statctx { |
87 |
|
|
TAILQ_ENTRY(statctx) entry; |
88 |
|
|
struct timeval t_start, t_last; |
89 |
|
|
unsigned long long bytes; |
90 |
|
|
int fd; |
91 |
|
|
char *buf; |
92 |
|
|
size_t buflen; |
93 |
|
|
struct event ev; |
94 |
|
|
/* TCP only */ |
95 |
|
|
struct tcpservsock *tcp_ts; |
96 |
|
|
u_long tcp_tcbaddr; |
97 |
|
|
/* UDP only */ |
98 |
|
|
u_long udp_slice_pkts; |
99 |
|
|
}; |
100 |
|
|
|
101 |
|
|
static void signal_handler(int, short, void *); |
102 |
|
|
static void saddr_ntop(const struct sockaddr *, socklen_t, char *, size_t); |
103 |
|
|
static void drop_gid(void); |
104 |
|
|
static void set_slice_timer(int); |
105 |
|
|
static void print_tcp_header(void); |
106 |
|
|
static void kget(u_long, void *, size_t); |
107 |
|
|
static u_long kfind_tcb(int); |
108 |
|
|
static void kupdate_stats(u_long, struct inpcb *, struct tcpcb *, |
109 |
|
|
struct socket *); |
110 |
|
|
static void list_kvars(void); |
111 |
|
|
static void check_kvar(const char *); |
112 |
|
|
static char ** check_prepare_kvars(char *); |
113 |
|
|
static void stats_prepare(struct statctx *); |
114 |
|
|
static void tcp_stats_display(unsigned long long, long double, float, |
115 |
|
|
struct statctx *, struct inpcb *, struct tcpcb *, struct socket *); |
116 |
|
|
static void tcp_process_slice(int, short, void *); |
117 |
|
|
static void tcp_server_handle_sc(int, short, void *); |
118 |
|
|
static void tcp_server_accept(int, short, void *); |
119 |
|
|
static void server_init(struct addrinfo *, struct statctx *); |
120 |
|
|
static void client_handle_sc(int, short, void *); |
121 |
|
|
static void client_init(struct addrinfo *, int, struct statctx *, |
122 |
|
|
struct addrinfo *); |
123 |
|
|
static int clock_gettime_tv(clockid_t, struct timeval *); |
124 |
|
|
static void udp_server_handle_sc(int, short, void *); |
125 |
|
|
static void udp_process_slice(int, short, void *); |
126 |
|
|
static int map_tos(char *, int *); |
127 |
|
|
/* |
128 |
|
|
* We account the mainstats here, that is the stats |
129 |
|
|
* for all connections, all variables starting with slice |
130 |
|
|
* are used to account information for the timeslice |
131 |
|
|
* between each output. Peak variables record the highest |
132 |
|
|
* between all slices so far. |
133 |
|
|
*/ |
134 |
|
|
static struct { |
135 |
|
|
unsigned long long slice_bytes; /* bytes for last slice */ |
136 |
|
|
long double peak_mbps; /* peak mbps so far */ |
137 |
|
|
int nconns; /* connected clients */ |
138 |
|
|
struct event timer; /* process timer */ |
139 |
|
|
} mainstats; |
140 |
|
|
|
141 |
|
|
/* When adding variables, also add to tcp_stats_display() */ |
142 |
|
|
static const char *allowed_kvars[] = { |
143 |
|
|
"inpcb.inp_flags", |
144 |
|
|
"sockb.so_rcv.sb_cc", |
145 |
|
|
"sockb.so_rcv.sb_hiwat", |
146 |
|
|
"sockb.so_rcv.sb_wat", |
147 |
|
|
"sockb.so_snd.sb_cc", |
148 |
|
|
"sockb.so_snd.sb_hiwat", |
149 |
|
|
"sockb.so_snd.sb_wat", |
150 |
|
|
"tcpcb.last_ack_sent", |
151 |
|
|
"tcpcb.max_sndwnd", |
152 |
|
|
"tcpcb.rcv_adv", |
153 |
|
|
"tcpcb.rcv_nxt", |
154 |
|
|
"tcpcb.rcv_scale", |
155 |
|
|
"tcpcb.rcv_wnd", |
156 |
|
|
"tcpcb.rfbuf_cnt", |
157 |
|
|
"tcpcb.rfbuf_ts", |
158 |
|
|
"tcpcb.snd_cwnd", |
159 |
|
|
"tcpcb.snd_max", |
160 |
|
|
"tcpcb.snd_nxt", |
161 |
|
|
"tcpcb.snd_scale", |
162 |
|
|
"tcpcb.snd_ssthresh", |
163 |
|
|
"tcpcb.snd_una", |
164 |
|
|
"tcpcb.snd_wl1", |
165 |
|
|
"tcpcb.snd_wl2", |
166 |
|
|
"tcpcb.snd_wnd", |
167 |
|
|
"tcpcb.t_rcvtime", |
168 |
|
|
"tcpcb.t_rtseq", |
169 |
|
|
"tcpcb.t_rttmin", |
170 |
|
|
"tcpcb.t_rtttime", |
171 |
|
|
"tcpcb.t_rttvar", |
172 |
|
|
"tcpcb.t_srtt", |
173 |
|
|
"tcpcb.ts_recent", |
174 |
|
|
"tcpcb.ts_recent_age", |
175 |
|
|
NULL |
176 |
|
|
}; |
177 |
|
|
|
178 |
|
|
TAILQ_HEAD(, statctx) sc_queue; |
179 |
|
|
|
180 |
|
|
static void __dead |
181 |
|
|
usage(void) |
182 |
|
|
{ |
183 |
|
|
fprintf(stderr, |
184 |
|
|
"usage: tcpbench -l\n" |
185 |
|
|
" tcpbench [-46RUuv] [-B buf] [-b addr] [-k kvars] [-n connections]\n" |
186 |
|
|
" [-p port] [-r interval] [-S space] [-T toskeyword]\n" |
187 |
|
|
" [-t secs] [-V rtable] hostname\n" |
188 |
|
|
" tcpbench -s [-46Uuv] [-B buf] [-k kvars] [-p port] [-r interval]\n" |
189 |
|
|
" [-S space] [-T toskeyword] [-V rtable] [hostname]\n"); |
190 |
|
|
exit(1); |
191 |
|
|
} |
192 |
|
|
|
193 |
|
|
static void |
194 |
|
|
signal_handler(int sig, short event, void *bula) |
195 |
|
|
{ |
196 |
|
|
/* |
197 |
|
|
* signal handler rules don't apply, libevent decouples for us |
198 |
|
|
*/ |
199 |
|
|
switch (sig) { |
200 |
|
|
case SIGINT: |
201 |
|
|
case SIGTERM: |
202 |
|
|
case SIGHUP: |
203 |
|
|
warnx("Terminated by signal %d", sig); |
204 |
|
|
exit(0); |
205 |
|
|
break; /* NOTREACHED */ |
206 |
|
|
default: |
207 |
|
|
errx(1, "unexpected signal %d", sig); |
208 |
|
|
break; /* NOTREACHED */ |
209 |
|
|
} |
210 |
|
|
} |
211 |
|
|
|
212 |
|
|
static void |
213 |
|
|
saddr_ntop(const struct sockaddr *addr, socklen_t alen, char *buf, size_t len) |
214 |
|
|
{ |
215 |
|
|
char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV]; |
216 |
|
|
int herr; |
217 |
|
|
|
218 |
|
|
if (addr->sa_family == AF_UNIX) { |
219 |
|
|
struct sockaddr_un *sun = (struct sockaddr_un *)addr; |
220 |
|
|
snprintf(buf, len, "%s", sun->sun_path); |
221 |
|
|
return; |
222 |
|
|
} |
223 |
|
|
if ((herr = getnameinfo(addr, alen, hbuf, sizeof(hbuf), |
224 |
|
|
pbuf, sizeof(pbuf), NI_NUMERICHOST|NI_NUMERICSERV)) != 0) { |
225 |
|
|
if (herr == EAI_SYSTEM) |
226 |
|
|
err(1, "getnameinfo"); |
227 |
|
|
else |
228 |
|
|
errx(1, "getnameinfo: %s", gai_strerror(herr)); |
229 |
|
|
} |
230 |
|
|
snprintf(buf, len, "[%s]:%s", hbuf, pbuf); |
231 |
|
|
} |
232 |
|
|
|
233 |
|
|
static void |
234 |
|
|
drop_gid(void) |
235 |
|
|
{ |
236 |
|
|
gid_t gid; |
237 |
|
|
|
238 |
|
|
gid = getgid(); |
239 |
|
|
if (setresgid(gid, gid, gid) == -1) |
240 |
|
|
err(1, "setresgid"); |
241 |
|
|
} |
242 |
|
|
|
243 |
|
|
static void |
244 |
|
|
set_slice_timer(int on) |
245 |
|
|
{ |
246 |
|
|
struct timeval tv; |
247 |
|
|
|
248 |
|
|
if (ptb->rflag == 0) |
249 |
|
|
return; |
250 |
|
|
|
251 |
|
|
if (on) { |
252 |
|
|
if (evtimer_pending(&mainstats.timer, NULL)) |
253 |
|
|
return; |
254 |
|
|
timerclear(&tv); |
255 |
|
|
/* XXX Is there a better way to do this ? */ |
256 |
|
|
tv.tv_sec = ptb->rflag / 1000; |
257 |
|
|
tv.tv_usec = (ptb->rflag % 1000) * 1000; |
258 |
|
|
|
259 |
|
|
evtimer_add(&mainstats.timer, &tv); |
260 |
|
|
} else if (evtimer_pending(&mainstats.timer, NULL)) |
261 |
|
|
evtimer_del(&mainstats.timer); |
262 |
|
|
} |
263 |
|
|
|
264 |
|
|
static int |
265 |
|
|
clock_gettime_tv(clockid_t clock_id, struct timeval *tv) |
266 |
|
|
{ |
267 |
|
|
struct timespec ts; |
268 |
|
|
|
269 |
|
|
if (clock_gettime(clock_id, &ts) == -1) |
270 |
|
|
return (-1); |
271 |
|
|
|
272 |
|
|
TIMESPEC_TO_TIMEVAL(tv, &ts); |
273 |
|
|
|
274 |
|
|
return (0); |
275 |
|
|
} |
276 |
|
|
|
277 |
|
|
static void |
278 |
|
|
print_tcp_header(void) |
279 |
|
|
{ |
280 |
|
|
char **kv; |
281 |
|
|
|
282 |
|
|
printf("%12s %14s %12s %8s ", "elapsed_ms", "bytes", "mbps", |
283 |
|
|
"bwidth"); |
284 |
|
|
for (kv = ptb->kvars; ptb->kvars != NULL && *kv != NULL; kv++) |
285 |
|
|
printf("%s%s", kv != ptb->kvars ? "," : "", *kv); |
286 |
|
|
printf("\n"); |
287 |
|
|
} |
288 |
|
|
|
289 |
|
|
static void |
290 |
|
|
kget(u_long addr, void *buf, size_t size) |
291 |
|
|
{ |
292 |
|
|
if (kvm_read(ptb->kvmh, addr, buf, size) != (ssize_t)size) |
293 |
|
|
errx(1, "kvm_read: %s", kvm_geterr(ptb->kvmh)); |
294 |
|
|
} |
295 |
|
|
|
296 |
|
|
static u_long |
297 |
|
|
kfind_tcb(int sock) |
298 |
|
|
{ |
299 |
|
|
struct inpcbtable tcbtab; |
300 |
|
|
struct inpcb *next, *prev; |
301 |
|
|
struct inpcb inpcb, prevpcb; |
302 |
|
|
struct tcpcb tcpcb; |
303 |
|
|
|
304 |
|
|
struct sockaddr_storage me, them; |
305 |
|
|
socklen_t melen, themlen; |
306 |
|
|
struct sockaddr_in *in4; |
307 |
|
|
struct sockaddr_in6 *in6; |
308 |
|
|
char tmp1[64], tmp2[64]; |
309 |
|
|
int nretry; |
310 |
|
|
|
311 |
|
|
nretry = 10; |
312 |
|
|
melen = themlen = sizeof(struct sockaddr_storage); |
313 |
|
|
if (getsockname(sock, (struct sockaddr *)&me, &melen) == -1) |
314 |
|
|
err(1, "getsockname"); |
315 |
|
|
if (getpeername(sock, (struct sockaddr *)&them, &themlen) == -1) |
316 |
|
|
err(1, "getpeername"); |
317 |
|
|
if (me.ss_family != them.ss_family) |
318 |
|
|
errx(1, "%s: me.ss_family != them.ss_family", __func__); |
319 |
|
|
if (me.ss_family != AF_INET && me.ss_family != AF_INET6) |
320 |
|
|
errx(1, "%s: unknown socket family", __func__); |
321 |
|
|
if (ptb->vflag >= 2) { |
322 |
|
|
saddr_ntop((struct sockaddr *)&me, me.ss_len, |
323 |
|
|
tmp1, sizeof(tmp1)); |
324 |
|
|
saddr_ntop((struct sockaddr *)&them, them.ss_len, |
325 |
|
|
tmp2, sizeof(tmp2)); |
326 |
|
|
fprintf(stderr, "Our socket local %s remote %s\n", tmp1, tmp2); |
327 |
|
|
} |
328 |
|
|
if (ptb->vflag >= 2) |
329 |
|
|
fprintf(stderr, "Using PCB table at %lu\n", ptb->ktcbtab); |
330 |
|
|
retry: |
331 |
|
|
kget(ptb->ktcbtab, &tcbtab, sizeof(tcbtab)); |
332 |
|
|
prev = NULL; |
333 |
|
|
next = TAILQ_FIRST(&tcbtab.inpt_queue); |
334 |
|
|
|
335 |
|
|
if (ptb->vflag >= 2) |
336 |
|
|
fprintf(stderr, "PCB start at %p\n", next); |
337 |
|
|
while (next != NULL) { |
338 |
|
|
if (ptb->vflag >= 2) |
339 |
|
|
fprintf(stderr, "Checking PCB %p\n", next); |
340 |
|
|
kget((u_long)next, &inpcb, sizeof(inpcb)); |
341 |
|
|
if (prev != NULL) { |
342 |
|
|
kget((u_long)prev, &prevpcb, sizeof(prevpcb)); |
343 |
|
|
if (TAILQ_NEXT(&prevpcb, inp_queue) != next) { |
344 |
|
|
if (nretry--) { |
345 |
|
|
warnx("PCB prev pointer insane"); |
346 |
|
|
goto retry; |
347 |
|
|
} else |
348 |
|
|
errx(1, "PCB prev pointer insane," |
349 |
|
|
" all attempts exhaused"); |
350 |
|
|
} |
351 |
|
|
} |
352 |
|
|
prev = next; |
353 |
|
|
next = TAILQ_NEXT(&inpcb, inp_queue); |
354 |
|
|
|
355 |
|
|
if (me.ss_family == AF_INET) { |
356 |
|
|
if ((inpcb.inp_flags & INP_IPV6) != 0) { |
357 |
|
|
if (ptb->vflag >= 2) |
358 |
|
|
fprintf(stderr, "Skip: INP_IPV6"); |
359 |
|
|
continue; |
360 |
|
|
} |
361 |
|
|
if (ptb->vflag >= 2) { |
362 |
|
|
inet_ntop(AF_INET, &inpcb.inp_laddr, |
363 |
|
|
tmp1, sizeof(tmp1)); |
364 |
|
|
inet_ntop(AF_INET, &inpcb.inp_faddr, |
365 |
|
|
tmp2, sizeof(tmp2)); |
366 |
|
|
fprintf(stderr, "PCB %p local: [%s]:%d " |
367 |
|
|
"remote: [%s]:%d\n", prev, |
368 |
|
|
tmp1, inpcb.inp_lport, |
369 |
|
|
tmp2, inpcb.inp_fport); |
370 |
|
|
} |
371 |
|
|
in4 = (struct sockaddr_in *)&me; |
372 |
|
|
if (memcmp(&in4->sin_addr, &inpcb.inp_laddr, |
373 |
|
|
sizeof(struct in_addr)) != 0 || |
374 |
|
|
in4->sin_port != inpcb.inp_lport) |
375 |
|
|
continue; |
376 |
|
|
in4 = (struct sockaddr_in *)&them; |
377 |
|
|
if (memcmp(&in4->sin_addr, &inpcb.inp_faddr, |
378 |
|
|
sizeof(struct in_addr)) != 0 || |
379 |
|
|
in4->sin_port != inpcb.inp_fport) |
380 |
|
|
continue; |
381 |
|
|
} else { |
382 |
|
|
if ((inpcb.inp_flags & INP_IPV6) == 0) |
383 |
|
|
continue; |
384 |
|
|
if (ptb->vflag >= 2) { |
385 |
|
|
inet_ntop(AF_INET6, &inpcb.inp_laddr6, |
386 |
|
|
tmp1, sizeof(tmp1)); |
387 |
|
|
inet_ntop(AF_INET6, &inpcb.inp_faddr6, |
388 |
|
|
tmp2, sizeof(tmp2)); |
389 |
|
|
fprintf(stderr, "PCB %p local: [%s]:%d " |
390 |
|
|
"remote: [%s]:%d\n", prev, |
391 |
|
|
tmp1, inpcb.inp_lport, |
392 |
|
|
tmp2, inpcb.inp_fport); |
393 |
|
|
} |
394 |
|
|
in6 = (struct sockaddr_in6 *)&me; |
395 |
|
|
if (memcmp(&in6->sin6_addr, &inpcb.inp_laddr6, |
396 |
|
|
sizeof(struct in6_addr)) != 0 || |
397 |
|
|
in6->sin6_port != inpcb.inp_lport) |
398 |
|
|
continue; |
399 |
|
|
in6 = (struct sockaddr_in6 *)&them; |
400 |
|
|
if (memcmp(&in6->sin6_addr, &inpcb.inp_faddr6, |
401 |
|
|
sizeof(struct in6_addr)) != 0 || |
402 |
|
|
in6->sin6_port != inpcb.inp_fport) |
403 |
|
|
continue; |
404 |
|
|
} |
405 |
|
|
kget((u_long)inpcb.inp_ppcb, &tcpcb, sizeof(tcpcb)); |
406 |
|
|
if (tcpcb.t_state != TCPS_ESTABLISHED) { |
407 |
|
|
if (ptb->vflag >= 2) |
408 |
|
|
fprintf(stderr, "Not established\n"); |
409 |
|
|
continue; |
410 |
|
|
} |
411 |
|
|
if (ptb->vflag >= 2) |
412 |
|
|
fprintf(stderr, "Found PCB at %p\n", prev); |
413 |
|
|
return ((u_long)prev); |
414 |
|
|
} |
415 |
|
|
|
416 |
|
|
errx(1, "No matching PCB found"); |
417 |
|
|
} |
418 |
|
|
|
419 |
|
|
static void |
420 |
|
|
kupdate_stats(u_long tcbaddr, struct inpcb *inpcb, |
421 |
|
|
struct tcpcb *tcpcb, struct socket *sockb) |
422 |
|
|
{ |
423 |
|
|
kget(tcbaddr, inpcb, sizeof(*inpcb)); |
424 |
|
|
kget((u_long)inpcb->inp_ppcb, tcpcb, sizeof(*tcpcb)); |
425 |
|
|
kget((u_long)inpcb->inp_socket, sockb, sizeof(*sockb)); |
426 |
|
|
} |
427 |
|
|
|
428 |
|
|
static void |
429 |
|
|
check_kvar(const char *var) |
430 |
|
|
{ |
431 |
|
|
u_int i; |
432 |
|
|
|
433 |
|
|
for (i = 0; allowed_kvars[i] != NULL; i++) |
434 |
|
|
if (strcmp(allowed_kvars[i], var) == 0) |
435 |
|
|
return; |
436 |
|
|
errx(1, "Unrecognised kvar: %s", var); |
437 |
|
|
} |
438 |
|
|
|
439 |
|
|
static void |
440 |
|
|
list_kvars(void) |
441 |
|
|
{ |
442 |
|
|
u_int i; |
443 |
|
|
|
444 |
|
|
printf("Supported kernel variables:\n"); |
445 |
|
|
for (i = 0; allowed_kvars[i] != NULL; i++) |
446 |
|
|
printf("\t%s\n", allowed_kvars[i]); |
447 |
|
|
} |
448 |
|
|
|
449 |
|
|
static char ** |
450 |
|
|
check_prepare_kvars(char *list) |
451 |
|
|
{ |
452 |
|
|
char *item, **ret = NULL; |
453 |
|
|
u_int n = 0; |
454 |
|
|
|
455 |
|
|
while ((item = strsep(&list, ", \t\n")) != NULL) { |
456 |
|
|
check_kvar(item); |
457 |
|
|
if ((ret = reallocarray(ret, (++n + 1), sizeof(*ret))) == NULL) |
458 |
|
|
errx(1, "reallocarray(kvars)"); |
459 |
|
|
if ((ret[n - 1] = strdup(item)) == NULL) |
460 |
|
|
errx(1, "strdup"); |
461 |
|
|
ret[n] = NULL; |
462 |
|
|
} |
463 |
|
|
return (ret); |
464 |
|
|
} |
465 |
|
|
|
466 |
|
|
static void |
467 |
|
|
stats_prepare(struct statctx *sc) |
468 |
|
|
{ |
469 |
|
|
sc->buf = ptb->dummybuf; |
470 |
|
|
sc->buflen = ptb->dummybuf_len; |
471 |
|
|
|
472 |
|
|
if (ptb->kvars) |
473 |
|
|
sc->tcp_tcbaddr = kfind_tcb(sc->fd); |
474 |
|
|
if (clock_gettime_tv(CLOCK_MONOTONIC, &sc->t_start) == -1) |
475 |
|
|
err(1, "clock_gettime_tv"); |
476 |
|
|
sc->t_last = sc->t_start; |
477 |
|
|
|
478 |
|
|
} |
479 |
|
|
|
480 |
|
|
static void |
481 |
|
|
tcp_stats_display(unsigned long long total_elapsed, long double mbps, |
482 |
|
|
float bwperc, struct statctx *sc, struct inpcb *inpcb, |
483 |
|
|
struct tcpcb *tcpcb, struct socket *sockb) |
484 |
|
|
{ |
485 |
|
|
int j; |
486 |
|
|
|
487 |
|
|
printf("%12llu %14llu %12.3Lf %7.2f%% ", total_elapsed, sc->bytes, |
488 |
|
|
mbps, bwperc); |
489 |
|
|
|
490 |
|
|
if (ptb->kvars != NULL) { |
491 |
|
|
kupdate_stats(sc->tcp_tcbaddr, inpcb, tcpcb, |
492 |
|
|
sockb); |
493 |
|
|
|
494 |
|
|
for (j = 0; ptb->kvars[j] != NULL; j++) { |
495 |
|
|
#define S(a) #a |
496 |
|
|
#define P(b, v, f) \ |
497 |
|
|
if (strcmp(ptb->kvars[j], S(b.v)) == 0) { \ |
498 |
|
|
printf("%s"f, j > 0 ? "," : "", b->v); \ |
499 |
|
|
continue; \ |
500 |
|
|
} |
501 |
|
|
P(inpcb, inp_flags, "0x%08x") |
502 |
|
|
P(sockb, so_rcv.sb_cc, "%lu") |
503 |
|
|
P(sockb, so_rcv.sb_hiwat, "%lu") |
504 |
|
|
P(sockb, so_rcv.sb_wat, "%lu") |
505 |
|
|
P(sockb, so_snd.sb_cc, "%lu") |
506 |
|
|
P(sockb, so_snd.sb_hiwat, "%lu") |
507 |
|
|
P(sockb, so_snd.sb_wat, "%lu") |
508 |
|
|
P(tcpcb, last_ack_sent, "%u") |
509 |
|
|
P(tcpcb, max_sndwnd, "%lu") |
510 |
|
|
P(tcpcb, rcv_adv, "%u") |
511 |
|
|
P(tcpcb, rcv_nxt, "%u") |
512 |
|
|
P(tcpcb, rcv_scale, "%u") |
513 |
|
|
P(tcpcb, rcv_wnd, "%lu") |
514 |
|
|
P(tcpcb, rfbuf_cnt, "%u") |
515 |
|
|
P(tcpcb, rfbuf_ts, "%u") |
516 |
|
|
P(tcpcb, snd_cwnd, "%lu") |
517 |
|
|
P(tcpcb, snd_max, "%u") |
518 |
|
|
P(tcpcb, snd_nxt, "%u") |
519 |
|
|
P(tcpcb, snd_scale, "%u") |
520 |
|
|
P(tcpcb, snd_ssthresh, "%lu") |
521 |
|
|
P(tcpcb, snd_una, "%u") |
522 |
|
|
P(tcpcb, snd_wl1, "%u") |
523 |
|
|
P(tcpcb, snd_wl2, "%u") |
524 |
|
|
P(tcpcb, snd_wnd, "%lu") |
525 |
|
|
P(tcpcb, t_rcvtime, "%u") |
526 |
|
|
P(tcpcb, t_rtseq, "%u") |
527 |
|
|
P(tcpcb, t_rttmin, "%hu") |
528 |
|
|
P(tcpcb, t_rtttime, "%u") |
529 |
|
|
P(tcpcb, t_rttvar, "%hu") |
530 |
|
|
P(tcpcb, t_srtt, "%hu") |
531 |
|
|
P(tcpcb, ts_recent, "%u") |
532 |
|
|
P(tcpcb, ts_recent_age, "%u") |
533 |
|
|
#undef S |
534 |
|
|
#undef P |
535 |
|
|
} |
536 |
|
|
} |
537 |
|
|
printf("\n"); |
538 |
|
|
} |
539 |
|
|
|
540 |
|
|
static void |
541 |
|
|
tcp_process_slice(int fd, short event, void *bula) |
542 |
|
|
{ |
543 |
|
|
unsigned long long total_elapsed, since_last; |
544 |
|
|
long double mbps, slice_mbps = 0; |
545 |
|
|
float bwperc; |
546 |
|
|
struct statctx *sc; |
547 |
|
|
struct timeval t_cur, t_diff; |
548 |
|
|
struct inpcb inpcb; |
549 |
|
|
struct tcpcb tcpcb; |
550 |
|
|
struct socket sockb; |
551 |
|
|
|
552 |
|
|
TAILQ_FOREACH(sc, &sc_queue, entry) { |
553 |
|
|
if (clock_gettime_tv(CLOCK_MONOTONIC, &t_cur) == -1) |
554 |
|
|
err(1, "clock_gettime_tv"); |
555 |
|
|
if (ptb->kvars != NULL) /* process kernel stats */ |
556 |
|
|
kupdate_stats(sc->tcp_tcbaddr, &inpcb, &tcpcb, |
557 |
|
|
&sockb); |
558 |
|
|
|
559 |
|
|
timersub(&t_cur, &sc->t_start, &t_diff); |
560 |
|
|
total_elapsed = t_diff.tv_sec * 1000 + t_diff.tv_usec / 1000; |
561 |
|
|
timersub(&t_cur, &sc->t_last, &t_diff); |
562 |
|
|
since_last = t_diff.tv_sec * 1000 + t_diff.tv_usec / 1000; |
563 |
|
|
bwperc = (sc->bytes * 100.0) / mainstats.slice_bytes; |
564 |
|
|
mbps = (sc->bytes * 8) / (since_last * 1000.0); |
565 |
|
|
slice_mbps += mbps; |
566 |
|
|
|
567 |
|
|
tcp_stats_display(total_elapsed, mbps, bwperc, sc, |
568 |
|
|
&inpcb, &tcpcb, &sockb); |
569 |
|
|
|
570 |
|
|
sc->t_last = t_cur; |
571 |
|
|
sc->bytes = 0; |
572 |
|
|
} |
573 |
|
|
|
574 |
|
|
/* process stats for this slice */ |
575 |
|
|
if (slice_mbps > mainstats.peak_mbps) |
576 |
|
|
mainstats.peak_mbps = slice_mbps; |
577 |
|
|
printf("Conn: %3d Mbps: %12.3Lf Peak Mbps: %12.3Lf Avg Mbps: %12.3Lf\n", |
578 |
|
|
mainstats.nconns, slice_mbps, mainstats.peak_mbps, |
579 |
|
|
mainstats.nconns ? slice_mbps / mainstats.nconns : 0); |
580 |
|
|
mainstats.slice_bytes = 0; |
581 |
|
|
|
582 |
|
|
set_slice_timer(mainstats.nconns > 0); |
583 |
|
|
} |
584 |
|
|
|
585 |
|
|
static void |
586 |
|
|
udp_process_slice(int fd, short event, void *v_sc) |
587 |
|
|
{ |
588 |
|
|
struct statctx *sc = v_sc; |
589 |
|
|
unsigned long long total_elapsed, since_last, pps; |
590 |
|
|
long double slice_mbps; |
591 |
|
|
struct timeval t_cur, t_diff; |
592 |
|
|
|
593 |
|
|
if (clock_gettime_tv(CLOCK_MONOTONIC, &t_cur) == -1) |
594 |
|
|
err(1, "clock_gettime_tv"); |
595 |
|
|
/* Calculate pps */ |
596 |
|
|
timersub(&t_cur, &sc->t_start, &t_diff); |
597 |
|
|
total_elapsed = t_diff.tv_sec * 1000 + t_diff.tv_usec / 1000; |
598 |
|
|
timersub(&t_cur, &sc->t_last, &t_diff); |
599 |
|
|
since_last = t_diff.tv_sec * 1000 + t_diff.tv_usec / 1000; |
600 |
|
|
slice_mbps = (sc->bytes * 8) / (since_last * 1000.0); |
601 |
|
|
pps = (sc->udp_slice_pkts * 1000) / since_last; |
602 |
|
|
if (slice_mbps > mainstats.peak_mbps) |
603 |
|
|
mainstats.peak_mbps = slice_mbps; |
604 |
|
|
printf("Elapsed: %11llu Mbps: %11.3Lf Peak Mbps: %11.3Lf %s PPS: %7llu\n", |
605 |
|
|
total_elapsed, slice_mbps, mainstats.peak_mbps, |
606 |
|
|
ptb->sflag ? "Rx" : "Tx", pps); |
607 |
|
|
|
608 |
|
|
/* Clean up this slice time */ |
609 |
|
|
sc->t_last = t_cur; |
610 |
|
|
sc->bytes = 0; |
611 |
|
|
sc->udp_slice_pkts = 0; |
612 |
|
|
set_slice_timer(1); |
613 |
|
|
} |
614 |
|
|
|
615 |
|
|
static void |
616 |
|
|
udp_server_handle_sc(int fd, short event, void *v_sc) |
617 |
|
|
{ |
618 |
|
|
ssize_t n; |
619 |
|
|
struct statctx *sc = v_sc; |
620 |
|
|
|
621 |
|
|
n = read(fd, ptb->dummybuf, ptb->dummybuf_len); |
622 |
|
|
if (n == 0) |
623 |
|
|
return; |
624 |
|
|
else if (n == -1) { |
625 |
|
|
if (errno != EINTR && errno != EWOULDBLOCK) |
626 |
|
|
warn("fd %d read error", fd); |
627 |
|
|
return; |
628 |
|
|
} |
629 |
|
|
|
630 |
|
|
if (ptb->vflag >= 3) |
631 |
|
|
fprintf(stderr, "read: %zd bytes\n", n); |
632 |
|
|
/* If this was our first packet, start slice timer */ |
633 |
|
|
if (mainstats.peak_mbps == 0) |
634 |
|
|
set_slice_timer(1); |
635 |
|
|
/* Account packet */ |
636 |
|
|
sc->udp_slice_pkts++; |
637 |
|
|
sc->bytes += n; |
638 |
|
|
} |
639 |
|
|
|
640 |
|
|
static void |
641 |
|
|
tcp_server_handle_sc(int fd, short event, void *v_sc) |
642 |
|
|
{ |
643 |
|
|
struct statctx *sc = v_sc; |
644 |
|
|
ssize_t n; |
645 |
|
|
|
646 |
|
|
n = read(sc->fd, sc->buf, sc->buflen); |
647 |
|
|
if (n == -1) { |
648 |
|
|
if (errno != EINTR && errno != EWOULDBLOCK) |
649 |
|
|
warn("fd %d read error", sc->fd); |
650 |
|
|
return; |
651 |
|
|
} else if (n == 0) { |
652 |
|
|
if (ptb->vflag) |
653 |
|
|
fprintf(stderr, "%8d closed by remote end\n", sc->fd); |
654 |
|
|
|
655 |
|
|
TAILQ_REMOVE(&sc_queue, sc, entry); |
656 |
|
|
|
657 |
|
|
event_del(&sc->ev); |
658 |
|
|
close(sc->fd); |
659 |
|
|
|
660 |
|
|
/* Some file descriptors are available again. */ |
661 |
|
|
if (evtimer_pending(&sc->tcp_ts->evt, NULL)) { |
662 |
|
|
evtimer_del(&sc->tcp_ts->evt); |
663 |
|
|
event_add(&sc->tcp_ts->ev, NULL); |
664 |
|
|
} |
665 |
|
|
|
666 |
|
|
free(sc); |
667 |
|
|
mainstats.nconns--; |
668 |
|
|
return; |
669 |
|
|
} |
670 |
|
|
if (ptb->vflag >= 3) |
671 |
|
|
fprintf(stderr, "read: %zd bytes\n", n); |
672 |
|
|
sc->bytes += n; |
673 |
|
|
mainstats.slice_bytes += n; |
674 |
|
|
} |
675 |
|
|
|
676 |
|
|
static void |
677 |
|
|
tcp_server_accept(int fd, short event, void *arg) |
678 |
|
|
{ |
679 |
|
|
struct tcpservsock *ts = arg; |
680 |
|
|
int sock; |
681 |
|
|
struct statctx *sc; |
682 |
|
|
struct sockaddr_storage ss; |
683 |
|
|
socklen_t sslen; |
684 |
|
|
char tmp[128]; |
685 |
|
|
|
686 |
|
|
sslen = sizeof(ss); |
687 |
|
|
|
688 |
|
|
event_add(&ts->ev, NULL); |
689 |
|
|
if (event & EV_TIMEOUT) |
690 |
|
|
return; |
691 |
|
|
if ((sock = accept4(fd, (struct sockaddr *)&ss, &sslen, SOCK_NONBLOCK)) |
692 |
|
|
== -1) { |
693 |
|
|
/* |
694 |
|
|
* Pause accept if we are out of file descriptors, or |
695 |
|
|
* libevent will haunt us here too. |
696 |
|
|
*/ |
697 |
|
|
if (errno == ENFILE || errno == EMFILE) { |
698 |
|
|
struct timeval evtpause = { 1, 0 }; |
699 |
|
|
|
700 |
|
|
event_del(&ts->ev); |
701 |
|
|
evtimer_add(&ts->evt, &evtpause); |
702 |
|
|
} else if (errno != EWOULDBLOCK && errno != EINTR && |
703 |
|
|
errno != ECONNABORTED) |
704 |
|
|
warn("accept"); |
705 |
|
|
return; |
706 |
|
|
} |
707 |
|
|
saddr_ntop((struct sockaddr *)&ss, sslen, |
708 |
|
|
tmp, sizeof(tmp)); |
709 |
|
|
if (ptb->Tflag != -1 && ss.ss_family == AF_INET) { |
710 |
|
|
if (setsockopt(sock, IPPROTO_IP, IP_TOS, |
711 |
|
|
&ptb->Tflag, sizeof(ptb->Tflag))) |
712 |
|
|
err(1, "setsockopt IP_TOS"); |
713 |
|
|
} |
714 |
|
|
if (ptb->Tflag != -1 && ss.ss_family == AF_INET6) { |
715 |
|
|
if (setsockopt(sock, IPPROTO_IPV6, IPV6_TCLASS, |
716 |
|
|
&ptb->Tflag, sizeof(ptb->Tflag))) |
717 |
|
|
err(1, "setsockopt IPV6_TCLASS"); |
718 |
|
|
} |
719 |
|
|
/* Alloc client structure and register reading callback */ |
720 |
|
|
if ((sc = calloc(1, sizeof(*sc))) == NULL) |
721 |
|
|
err(1, "calloc"); |
722 |
|
|
sc->tcp_ts = ts; |
723 |
|
|
sc->fd = sock; |
724 |
|
|
stats_prepare(sc); |
725 |
|
|
event_set(&sc->ev, sc->fd, EV_READ | EV_PERSIST, |
726 |
|
|
tcp_server_handle_sc, sc); |
727 |
|
|
event_add(&sc->ev, NULL); |
728 |
|
|
TAILQ_INSERT_TAIL(&sc_queue, sc, entry); |
729 |
|
|
mainstats.nconns++; |
730 |
|
|
if (mainstats.nconns == 1) |
731 |
|
|
set_slice_timer(1); |
732 |
|
|
if (ptb->vflag) |
733 |
|
|
fprintf(stderr, "Accepted connection from %s, fd = %d\n", |
734 |
|
|
tmp, sc->fd); |
735 |
|
|
} |
736 |
|
|
|
737 |
|
|
static void |
738 |
|
|
server_init(struct addrinfo *aitop, struct statctx *udp_sc) |
739 |
|
|
{ |
740 |
|
|
char tmp[128]; |
741 |
|
|
int sock, on = 1; |
742 |
|
|
struct addrinfo *ai; |
743 |
|
|
struct event *ev; |
744 |
|
|
struct tcpservsock *ts; |
745 |
|
|
nfds_t lnfds; |
746 |
|
|
|
747 |
|
|
lnfds = 0; |
748 |
|
|
for (ai = aitop; ai != NULL; ai = ai->ai_next) { |
749 |
|
|
saddr_ntop(ai->ai_addr, ai->ai_addrlen, tmp, sizeof(tmp)); |
750 |
|
|
if (ptb->vflag) |
751 |
|
|
fprintf(stderr, "Try to bind to %s\n", tmp); |
752 |
|
|
if ((sock = socket(ai->ai_family, ai->ai_socktype, |
753 |
|
|
ai->ai_protocol)) == -1) { |
754 |
|
|
if (ai->ai_next == NULL) |
755 |
|
|
err(1, "socket"); |
756 |
|
|
if (ptb->vflag) |
757 |
|
|
warn("socket"); |
758 |
|
|
continue; |
759 |
|
|
} |
760 |
|
|
if (ptb->Tflag != -1 && ai->ai_family == AF_INET) { |
761 |
|
|
if (setsockopt(sock, IPPROTO_IP, IP_TOS, |
762 |
|
|
&ptb->Tflag, sizeof(ptb->Tflag))) |
763 |
|
|
err(1, "setsockopt IP_TOS"); |
764 |
|
|
} |
765 |
|
|
if (ptb->Tflag != -1 && ai->ai_family == AF_INET6) { |
766 |
|
|
if (setsockopt(sock, IPPROTO_IPV6, IPV6_TCLASS, |
767 |
|
|
&ptb->Tflag, sizeof(ptb->Tflag))) |
768 |
|
|
err(1, "setsockopt IPV6_TCLASS"); |
769 |
|
|
} |
770 |
|
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, |
771 |
|
|
&on, sizeof(on)) == -1) |
772 |
|
|
warn("reuse port"); |
773 |
|
|
if (bind(sock, ai->ai_addr, ai->ai_addrlen) != 0) { |
774 |
|
|
if (ai->ai_next == NULL) |
775 |
|
|
err(1, "bind"); |
776 |
|
|
if (ptb->vflag) |
777 |
|
|
warn("bind"); |
778 |
|
|
close(sock); |
779 |
|
|
continue; |
780 |
|
|
} |
781 |
|
|
if (ptb->Sflag) { |
782 |
|
|
if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, |
783 |
|
|
&ptb->Sflag, sizeof(ptb->Sflag)) == -1) |
784 |
|
|
warn("set receive buffer size"); |
785 |
|
|
} |
786 |
|
|
if (TCP_MODE) { |
787 |
|
|
if (listen(sock, 64) == -1) { |
788 |
|
|
if (ai->ai_next == NULL) |
789 |
|
|
err(1, "listen"); |
790 |
|
|
if (ptb->vflag) |
791 |
|
|
warn("listen"); |
792 |
|
|
close(sock); |
793 |
|
|
continue; |
794 |
|
|
} |
795 |
|
|
} |
796 |
|
|
if (UDP_MODE) { |
797 |
|
|
if ((ev = calloc(1, sizeof(*ev))) == NULL) |
798 |
|
|
err(1, "calloc"); |
799 |
|
|
event_set(ev, sock, EV_READ | EV_PERSIST, |
800 |
|
|
udp_server_handle_sc, udp_sc); |
801 |
|
|
event_add(ev, NULL); |
802 |
|
|
} else { |
803 |
|
|
if ((ts = calloc(1, sizeof(*ts))) == NULL) |
804 |
|
|
err(1, "calloc"); |
805 |
|
|
|
806 |
|
|
ts->fd = sock; |
807 |
|
|
evtimer_set(&ts->evt, tcp_server_accept, ts); |
808 |
|
|
event_set(&ts->ev, ts->fd, EV_READ, |
809 |
|
|
tcp_server_accept, ts); |
810 |
|
|
event_add(&ts->ev, NULL); |
811 |
|
|
} |
812 |
|
|
if (ptb->vflag >= 3) |
813 |
|
|
fprintf(stderr, "bound to fd %d\n", sock); |
814 |
|
|
lnfds++; |
815 |
|
|
} |
816 |
|
|
if (!ptb->Uflag) |
817 |
|
|
freeaddrinfo(aitop); |
818 |
|
|
if (lnfds == 0) |
819 |
|
|
errx(1, "No working listen addresses found"); |
820 |
|
|
} |
821 |
|
|
|
822 |
|
|
static void |
823 |
|
|
client_handle_sc(int fd, short event, void *v_sc) |
824 |
|
|
{ |
825 |
|
|
struct statctx *sc = v_sc; |
826 |
|
|
ssize_t n; |
827 |
|
|
size_t blen = sc->buflen; |
828 |
|
|
|
829 |
|
|
if (ptb->Rflag) |
830 |
|
|
blen = arc4random_uniform(blen) + 1; |
831 |
|
|
if ((n = write(sc->fd, sc->buf, blen)) == -1) { |
832 |
|
|
if (errno == EINTR || errno == EWOULDBLOCK || |
833 |
|
|
(UDP_MODE && errno == ENOBUFS)) |
834 |
|
|
return; |
835 |
|
|
err(1, "write"); |
836 |
|
|
} |
837 |
|
|
if (TCP_MODE && n == 0) { |
838 |
|
|
fprintf(stderr, "Remote end closed connection"); |
839 |
|
|
exit(1); |
840 |
|
|
} |
841 |
|
|
if (ptb->vflag >= 3) |
842 |
|
|
fprintf(stderr, "write: %zd bytes\n", n); |
843 |
|
|
sc->bytes += n; |
844 |
|
|
mainstats.slice_bytes += n; |
845 |
|
|
if (UDP_MODE) |
846 |
|
|
sc->udp_slice_pkts++; |
847 |
|
|
} |
848 |
|
|
|
849 |
|
|
static void |
850 |
|
|
client_init(struct addrinfo *aitop, int nconn, struct statctx *udp_sc, |
851 |
|
|
struct addrinfo *aib) |
852 |
|
|
{ |
853 |
|
|
struct statctx *sc; |
854 |
|
|
struct addrinfo *ai; |
855 |
|
|
char tmp[128]; |
856 |
|
|
int i, r, sock; |
857 |
|
|
|
858 |
|
|
sc = udp_sc; |
859 |
|
|
for (i = 0; i < nconn; i++) { |
860 |
|
|
for (sock = -1, ai = aitop; ai != NULL; ai = ai->ai_next) { |
861 |
|
|
saddr_ntop(ai->ai_addr, ai->ai_addrlen, tmp, |
862 |
|
|
sizeof(tmp)); |
863 |
|
|
if (ptb->vflag && i == 0) |
864 |
|
|
fprintf(stderr, "Trying %s\n", tmp); |
865 |
|
|
if ((sock = socket(ai->ai_family, ai->ai_socktype, |
866 |
|
|
ai->ai_protocol)) == -1) { |
867 |
|
|
if (ai->ai_next == NULL) |
868 |
|
|
err(1, "socket"); |
869 |
|
|
if (ptb->vflag) |
870 |
|
|
warn("socket"); |
871 |
|
|
continue; |
872 |
|
|
} |
873 |
|
|
if (aib != NULL) { |
874 |
|
|
saddr_ntop(aib->ai_addr, aib->ai_addrlen, |
875 |
|
|
tmp, sizeof(tmp)); |
876 |
|
|
if (ptb->vflag) |
877 |
|
|
fprintf(stderr, |
878 |
|
|
"Try to bind to %s\n", tmp); |
879 |
|
|
if (bind(sock, (struct sockaddr *)aib->ai_addr, |
880 |
|
|
aib->ai_addrlen) == -1) |
881 |
|
|
err(1, "bind"); |
882 |
|
|
} |
883 |
|
|
if (ptb->Tflag != -1 && ai->ai_family == AF_INET) { |
884 |
|
|
if (setsockopt(sock, IPPROTO_IP, IP_TOS, |
885 |
|
|
&ptb->Tflag, sizeof(ptb->Tflag))) |
886 |
|
|
err(1, "setsockopt IP_TOS"); |
887 |
|
|
} |
888 |
|
|
if (ptb->Tflag != -1 && ai->ai_family == AF_INET6) { |
889 |
|
|
if (setsockopt(sock, IPPROTO_IPV6, IPV6_TCLASS, |
890 |
|
|
&ptb->Tflag, sizeof(ptb->Tflag))) |
891 |
|
|
err(1, "setsockopt IPV6_TCLASS"); |
892 |
|
|
} |
893 |
|
|
if (ptb->Sflag) { |
894 |
|
|
if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, |
895 |
|
|
&ptb->Sflag, sizeof(ptb->Sflag)) == -1) |
896 |
|
|
warn("set TCP send buffer size"); |
897 |
|
|
} |
898 |
|
|
if (connect(sock, ai->ai_addr, ai->ai_addrlen) != 0) { |
899 |
|
|
if (ai->ai_next == NULL) |
900 |
|
|
err(1, "connect"); |
901 |
|
|
if (ptb->vflag) |
902 |
|
|
warn("connect"); |
903 |
|
|
close(sock); |
904 |
|
|
sock = -1; |
905 |
|
|
continue; |
906 |
|
|
} |
907 |
|
|
break; |
908 |
|
|
} |
909 |
|
|
if (sock == -1) |
910 |
|
|
errx(1, "No host found"); |
911 |
|
|
if ((r = fcntl(sock, F_GETFL)) == -1) |
912 |
|
|
err(1, "fcntl(F_GETFL)"); |
913 |
|
|
r |= O_NONBLOCK; |
914 |
|
|
if (fcntl(sock, F_SETFL, r) == -1) |
915 |
|
|
err(1, "fcntl(F_SETFL, O_NONBLOCK)"); |
916 |
|
|
/* Alloc and prepare stats */ |
917 |
|
|
if (TCP_MODE) { |
918 |
|
|
if ((sc = calloc(1, sizeof(*sc))) == NULL) |
919 |
|
|
err(1, "calloc"); |
920 |
|
|
} |
921 |
|
|
sc->fd = sock; |
922 |
|
|
stats_prepare(sc); |
923 |
|
|
event_set(&sc->ev, sc->fd, EV_WRITE | EV_PERSIST, |
924 |
|
|
client_handle_sc, sc); |
925 |
|
|
event_add(&sc->ev, NULL); |
926 |
|
|
TAILQ_INSERT_TAIL(&sc_queue, sc, entry); |
927 |
|
|
mainstats.nconns++; |
928 |
|
|
if (mainstats.nconns == 1) |
929 |
|
|
set_slice_timer(1); |
930 |
|
|
} |
931 |
|
|
if (!ptb->Uflag) |
932 |
|
|
freeaddrinfo(aitop); |
933 |
|
|
if (aib != NULL) |
934 |
|
|
freeaddrinfo(aib); |
935 |
|
|
|
936 |
|
|
if (ptb->vflag && nconn > 1) |
937 |
|
|
fprintf(stderr, "%d connections established\n", |
938 |
|
|
mainstats.nconns); |
939 |
|
|
} |
940 |
|
|
|
941 |
|
|
static int |
942 |
|
|
map_tos(char *s, int *val) |
943 |
|
|
{ |
944 |
|
|
/* DiffServ Codepoints and other TOS mappings */ |
945 |
|
|
const struct toskeywords { |
946 |
|
|
const char *keyword; |
947 |
|
|
int val; |
948 |
|
|
} *t, toskeywords[] = { |
949 |
|
|
{ "af11", IPTOS_DSCP_AF11 }, |
950 |
|
|
{ "af12", IPTOS_DSCP_AF12 }, |
951 |
|
|
{ "af13", IPTOS_DSCP_AF13 }, |
952 |
|
|
{ "af21", IPTOS_DSCP_AF21 }, |
953 |
|
|
{ "af22", IPTOS_DSCP_AF22 }, |
954 |
|
|
{ "af23", IPTOS_DSCP_AF23 }, |
955 |
|
|
{ "af31", IPTOS_DSCP_AF31 }, |
956 |
|
|
{ "af32", IPTOS_DSCP_AF32 }, |
957 |
|
|
{ "af33", IPTOS_DSCP_AF33 }, |
958 |
|
|
{ "af41", IPTOS_DSCP_AF41 }, |
959 |
|
|
{ "af42", IPTOS_DSCP_AF42 }, |
960 |
|
|
{ "af43", IPTOS_DSCP_AF43 }, |
961 |
|
|
{ "critical", IPTOS_PREC_CRITIC_ECP }, |
962 |
|
|
{ "cs0", IPTOS_DSCP_CS0 }, |
963 |
|
|
{ "cs1", IPTOS_DSCP_CS1 }, |
964 |
|
|
{ "cs2", IPTOS_DSCP_CS2 }, |
965 |
|
|
{ "cs3", IPTOS_DSCP_CS3 }, |
966 |
|
|
{ "cs4", IPTOS_DSCP_CS4 }, |
967 |
|
|
{ "cs5", IPTOS_DSCP_CS5 }, |
968 |
|
|
{ "cs6", IPTOS_DSCP_CS6 }, |
969 |
|
|
{ "cs7", IPTOS_DSCP_CS7 }, |
970 |
|
|
{ "ef", IPTOS_DSCP_EF }, |
971 |
|
|
{ "inetcontrol", IPTOS_PREC_INTERNETCONTROL }, |
972 |
|
|
{ "lowdelay", IPTOS_LOWDELAY }, |
973 |
|
|
{ "netcontrol", IPTOS_PREC_NETCONTROL }, |
974 |
|
|
{ "reliability", IPTOS_RELIABILITY }, |
975 |
|
|
{ "throughput", IPTOS_THROUGHPUT }, |
976 |
|
|
{ NULL, -1 }, |
977 |
|
|
}; |
978 |
|
|
|
979 |
|
|
for (t = toskeywords; t->keyword != NULL; t++) { |
980 |
|
|
if (strcmp(s, t->keyword) == 0) { |
981 |
|
|
*val = t->val; |
982 |
|
|
return (1); |
983 |
|
|
} |
984 |
|
|
} |
985 |
|
|
|
986 |
|
|
return (0); |
987 |
|
|
} |
988 |
|
|
|
989 |
|
|
static void |
990 |
|
|
quit(int sig, short event, void *arg) |
991 |
|
|
{ |
992 |
|
|
exit(0); |
993 |
|
|
} |
994 |
|
|
|
995 |
|
|
int |
996 |
|
|
main(int argc, char **argv) |
997 |
|
|
{ |
998 |
|
|
struct timeval tv; |
999 |
|
|
unsigned int secs, rtable; |
1000 |
|
|
|
1001 |
|
|
char kerr[_POSIX2_LINE_MAX], *tmp; |
1002 |
|
|
struct addrinfo *aitop, *aib, hints; |
1003 |
|
|
const char *errstr; |
1004 |
|
|
struct rlimit rl; |
1005 |
|
|
int ch, herr, nconn; |
1006 |
|
|
int family = PF_UNSPEC; |
1007 |
|
|
struct nlist nl[] = { { "_tcbtable" }, { "" } }; |
1008 |
|
|
const char *host = NULL, *port = DEFAULT_PORT, *srcbind = NULL; |
1009 |
|
|
struct event ev_sigint, ev_sigterm, ev_sighup, ev_progtimer; |
1010 |
|
|
struct statctx *udp_sc = NULL; |
1011 |
|
|
struct sockaddr_un sock_un; |
1012 |
|
|
|
1013 |
|
|
/* Init world */ |
1014 |
|
|
setvbuf(stdout, NULL, _IOLBF, 0); |
1015 |
|
|
ptb = &tcpbench; |
1016 |
|
|
ptb->dummybuf_len = 0; |
1017 |
|
|
ptb->Sflag = ptb->sflag = ptb->vflag = ptb->Rflag = ptb->Uflag = 0; |
1018 |
|
|
ptb->kvmh = NULL; |
1019 |
|
|
ptb->kvars = NULL; |
1020 |
|
|
ptb->rflag = DEFAULT_STATS_INTERVAL; |
1021 |
|
|
ptb->Tflag = -1; |
1022 |
|
|
nconn = 1; |
1023 |
|
|
aib = NULL; |
1024 |
|
|
secs = 0; |
1025 |
|
|
|
1026 |
|
|
while ((ch = getopt(argc, argv, "46b:B:hlk:n:p:Rr:sS:t:T:uUvV:")) != -1) { |
1027 |
|
|
switch (ch) { |
1028 |
|
|
case '4': |
1029 |
|
|
family = PF_INET; |
1030 |
|
|
break; |
1031 |
|
|
case '6': |
1032 |
|
|
family = PF_INET6; |
1033 |
|
|
break; |
1034 |
|
|
case 'b': |
1035 |
|
|
srcbind = optarg; |
1036 |
|
|
break; |
1037 |
|
|
case 'l': |
1038 |
|
|
list_kvars(); |
1039 |
|
|
exit(0); |
1040 |
|
|
case 'k': |
1041 |
|
|
if ((tmp = strdup(optarg)) == NULL) |
1042 |
|
|
errx(1, "strdup"); |
1043 |
|
|
ptb->kvars = check_prepare_kvars(tmp); |
1044 |
|
|
free(tmp); |
1045 |
|
|
break; |
1046 |
|
|
case 'R': |
1047 |
|
|
ptb->Rflag = 1; |
1048 |
|
|
break; |
1049 |
|
|
case 'r': |
1050 |
|
|
ptb->rflag = strtonum(optarg, 0, 60 * 60 * 24 * 1000, |
1051 |
|
|
&errstr); |
1052 |
|
|
if (errstr != NULL) |
1053 |
|
|
errx(1, "statistics interval is %s: %s", |
1054 |
|
|
errstr, optarg); |
1055 |
|
|
break; |
1056 |
|
|
case 'p': |
1057 |
|
|
port = optarg; |
1058 |
|
|
break; |
1059 |
|
|
case 's': |
1060 |
|
|
ptb->sflag = 1; |
1061 |
|
|
break; |
1062 |
|
|
case 'S': |
1063 |
|
|
ptb->Sflag = strtonum(optarg, 0, 1024*1024*1024, |
1064 |
|
|
&errstr); |
1065 |
|
|
if (errstr != NULL) |
1066 |
|
|
errx(1, "receive space interval is %s: %s", |
1067 |
|
|
errstr, optarg); |
1068 |
|
|
break; |
1069 |
|
|
case 'B': |
1070 |
|
|
ptb->dummybuf_len = strtonum(optarg, 0, 1024*1024*1024, |
1071 |
|
|
&errstr); |
1072 |
|
|
if (errstr != NULL) |
1073 |
|
|
errx(1, "read/write buffer size is %s: %s", |
1074 |
|
|
errstr, optarg); |
1075 |
|
|
break; |
1076 |
|
|
case 'v': |
1077 |
|
|
ptb->vflag++; |
1078 |
|
|
break; |
1079 |
|
|
case 'V': |
1080 |
|
|
rtable = (unsigned int)strtonum(optarg, 0, |
1081 |
|
|
RT_TABLEID_MAX, &errstr); |
1082 |
|
|
if (errstr) |
1083 |
|
|
errx(1, "rtable value is %s: %s", |
1084 |
|
|
errstr, optarg); |
1085 |
|
|
if (setrtable(rtable) == -1) |
1086 |
|
|
err(1, "setrtable"); |
1087 |
|
|
break; |
1088 |
|
|
case 'n': |
1089 |
|
|
nconn = strtonum(optarg, 0, 65535, &errstr); |
1090 |
|
|
if (errstr != NULL) |
1091 |
|
|
errx(1, "number of connections is %s: %s", |
1092 |
|
|
errstr, optarg); |
1093 |
|
|
break; |
1094 |
|
|
case 'u': |
1095 |
|
|
ptb->uflag = 1; |
1096 |
|
|
break; |
1097 |
|
|
case 'U': |
1098 |
|
|
ptb->Uflag = 1; |
1099 |
|
|
break; |
1100 |
|
|
case 'T': |
1101 |
|
|
if (map_tos(optarg, &ptb->Tflag)) |
1102 |
|
|
break; |
1103 |
|
|
errstr = NULL; |
1104 |
|
|
if (strlen(optarg) > 1 && optarg[0] == '0' && |
1105 |
|
|
optarg[1] == 'x') |
1106 |
|
|
ptb->Tflag = (int)strtol(optarg, NULL, 16); |
1107 |
|
|
else |
1108 |
|
|
ptb->Tflag = (int)strtonum(optarg, 0, 255, |
1109 |
|
|
&errstr); |
1110 |
|
|
if (ptb->Tflag == -1 || ptb->Tflag > 255 || errstr) |
1111 |
|
|
errx(1, "illegal tos value %s", optarg); |
1112 |
|
|
break; |
1113 |
|
|
case 't': |
1114 |
|
|
secs = strtonum(optarg, 1, UINT_MAX, &errstr); |
1115 |
|
|
if (errstr != NULL) |
1116 |
|
|
errx(1, "secs is %s: %s", |
1117 |
|
|
errstr, optarg); |
1118 |
|
|
break; |
1119 |
|
|
case 'h': |
1120 |
|
|
default: |
1121 |
|
|
usage(); |
1122 |
|
|
} |
1123 |
|
|
} |
1124 |
|
|
|
1125 |
|
|
if (pledge("stdio rpath dns inet unix id proc wpath cpath", NULL) == -1) |
1126 |
|
|
err(1, "pledge"); |
1127 |
|
|
|
1128 |
|
|
argv += optind; |
1129 |
|
|
argc -= optind; |
1130 |
|
|
if ((argc != (ptb->sflag && !ptb->Uflag ? 0 : 1)) || |
1131 |
|
|
(UDP_MODE && (ptb->kvars || nconn != 1))) |
1132 |
|
|
usage(); |
1133 |
|
|
|
1134 |
|
|
if (ptb->kvars) { |
1135 |
|
|
if ((ptb->kvmh = kvm_openfiles(NULL, NULL, NULL, |
1136 |
|
|
O_RDONLY, kerr)) == NULL) |
1137 |
|
|
errx(1, "kvm_open: %s", kerr); |
1138 |
|
|
drop_gid(); |
1139 |
|
|
if (kvm_nlist(ptb->kvmh, nl) < 0 || nl[0].n_type == 0) |
1140 |
|
|
errx(1, "kvm: no namelist"); |
1141 |
|
|
ptb->ktcbtab = nl[0].n_value; |
1142 |
|
|
} else |
1143 |
|
|
drop_gid(); |
1144 |
|
|
|
1145 |
|
|
if (pledge("stdio id dns inet unix wpath cpath rpath", NULL) == -1) |
1146 |
|
|
err(1, "pledge"); |
1147 |
|
|
|
1148 |
|
|
if (!ptb->sflag || ptb->Uflag) |
1149 |
|
|
host = argv[0]; |
1150 |
|
|
/* |
1151 |
|
|
* Rationale, |
1152 |
|
|
* If TCP, use a big buffer with big reads/writes. |
1153 |
|
|
* If UDP, use a big buffer in server and a buffer the size of a |
1154 |
|
|
* ethernet packet. |
1155 |
|
|
*/ |
1156 |
|
|
if (!ptb->dummybuf_len) { |
1157 |
|
|
if (ptb->sflag || TCP_MODE) |
1158 |
|
|
ptb->dummybuf_len = DEFAULT_BUF; |
1159 |
|
|
else |
1160 |
|
|
ptb->dummybuf_len = DEFAULT_UDP_PKT; |
1161 |
|
|
} |
1162 |
|
|
|
1163 |
|
|
bzero(&hints, sizeof(hints)); |
1164 |
|
|
hints.ai_family = family; |
1165 |
|
|
if (UDP_MODE) { |
1166 |
|
|
hints.ai_socktype = SOCK_DGRAM; |
1167 |
|
|
hints.ai_protocol = IPPROTO_UDP; |
1168 |
|
|
} else { |
1169 |
|
|
hints.ai_socktype = SOCK_STREAM; |
1170 |
|
|
hints.ai_protocol = IPPROTO_TCP; |
1171 |
|
|
} |
1172 |
|
|
if (ptb->Uflag) { |
1173 |
|
|
hints.ai_family = AF_UNIX; |
1174 |
|
|
hints.ai_protocol = 0; |
1175 |
|
|
sock_un.sun_family = AF_UNIX; |
1176 |
|
|
if (strlcpy(sock_un.sun_path, host, sizeof(sock_un.sun_path)) >= |
1177 |
|
|
sizeof(sock_un.sun_path)) |
1178 |
|
|
errx(1, "socket name '%s' too long", host); |
1179 |
|
|
hints.ai_addr = (struct sockaddr *)&sock_un; |
1180 |
|
|
hints.ai_addrlen = sizeof(sock_un); |
1181 |
|
|
aitop = &hints; |
1182 |
|
|
} else { |
1183 |
|
|
if (ptb->sflag) |
1184 |
|
|
hints.ai_flags = AI_PASSIVE; |
1185 |
|
|
if (srcbind != NULL) { |
1186 |
|
|
hints.ai_flags |= AI_NUMERICHOST; |
1187 |
|
|
herr = getaddrinfo(srcbind, NULL, &hints, &aib); |
1188 |
|
|
hints.ai_flags &= ~AI_NUMERICHOST; |
1189 |
|
|
if (herr != 0) { |
1190 |
|
|
if (herr == EAI_SYSTEM) |
1191 |
|
|
err(1, "getaddrinfo"); |
1192 |
|
|
else |
1193 |
|
|
errx(1, "getaddrinfo: %s", |
1194 |
|
|
gai_strerror(herr)); |
1195 |
|
|
} |
1196 |
|
|
} |
1197 |
|
|
if ((herr = getaddrinfo(host, port, &hints, &aitop)) != 0) { |
1198 |
|
|
if (herr == EAI_SYSTEM) |
1199 |
|
|
err(1, "getaddrinfo"); |
1200 |
|
|
else |
1201 |
|
|
errx(1, "getaddrinfo: %s", gai_strerror(herr)); |
1202 |
|
|
} |
1203 |
|
|
} |
1204 |
|
|
|
1205 |
|
|
if (pledge("stdio id inet unix wpath cpath rpath", NULL) == -1) |
1206 |
|
|
err(1, "pledge"); |
1207 |
|
|
|
1208 |
|
|
if (getrlimit(RLIMIT_NOFILE, &rl) == -1) |
1209 |
|
|
err(1, "getrlimit"); |
1210 |
|
|
if (rl.rlim_cur < MAX_FD) |
1211 |
|
|
rl.rlim_cur = MAX_FD; |
1212 |
|
|
if (setrlimit(RLIMIT_NOFILE, &rl)) |
1213 |
|
|
err(1, "setrlimit"); |
1214 |
|
|
if (getrlimit(RLIMIT_NOFILE, &rl) == -1) |
1215 |
|
|
err(1, "getrlimit"); |
1216 |
|
|
|
1217 |
|
|
if (pledge("stdio inet unix wpath cpath rpath", NULL) == -1) |
1218 |
|
|
err(1, "pledge"); |
1219 |
|
|
|
1220 |
|
|
/* Init world */ |
1221 |
|
|
TAILQ_INIT(&sc_queue); |
1222 |
|
|
if ((ptb->dummybuf = malloc(ptb->dummybuf_len)) == NULL) |
1223 |
|
|
err(1, "malloc"); |
1224 |
|
|
arc4random_buf(ptb->dummybuf, ptb->dummybuf_len); |
1225 |
|
|
|
1226 |
|
|
/* Setup libevent and signals */ |
1227 |
|
|
event_init(); |
1228 |
|
|
signal_set(&ev_sigterm, SIGTERM, signal_handler, NULL); |
1229 |
|
|
signal_set(&ev_sighup, SIGHUP, signal_handler, NULL); |
1230 |
|
|
signal_set(&ev_sigint, SIGINT, signal_handler, NULL); |
1231 |
|
|
signal_add(&ev_sigint, NULL); |
1232 |
|
|
signal_add(&ev_sigterm, NULL); |
1233 |
|
|
signal_add(&ev_sighup, NULL); |
1234 |
|
|
signal(SIGPIPE, SIG_IGN); |
1235 |
|
|
|
1236 |
|
|
if (UDP_MODE) { |
1237 |
|
|
if ((udp_sc = calloc(1, sizeof(*udp_sc))) == NULL) |
1238 |
|
|
err(1, "calloc"); |
1239 |
|
|
udp_sc->fd = -1; |
1240 |
|
|
stats_prepare(udp_sc); |
1241 |
|
|
evtimer_set(&mainstats.timer, udp_process_slice, udp_sc); |
1242 |
|
|
} else { |
1243 |
|
|
print_tcp_header(); |
1244 |
|
|
evtimer_set(&mainstats.timer, tcp_process_slice, NULL); |
1245 |
|
|
} |
1246 |
|
|
|
1247 |
|
|
if (ptb->sflag) |
1248 |
|
|
server_init(aitop, udp_sc); |
1249 |
|
|
else { |
1250 |
|
|
if (secs > 0) { |
1251 |
|
|
timerclear(&tv); |
1252 |
|
|
tv.tv_sec = secs + 1; |
1253 |
|
|
evtimer_set(&ev_progtimer, quit, NULL); |
1254 |
|
|
evtimer_add(&ev_progtimer, &tv); |
1255 |
|
|
} |
1256 |
|
|
client_init(aitop, nconn, udp_sc, aib); |
1257 |
|
|
|
1258 |
|
|
if (pledge("stdio wpath cpath rpath", NULL) == -1) |
1259 |
|
|
err(1, "pledge"); |
1260 |
|
|
} |
1261 |
|
|
|
1262 |
|
|
/* libevent main loop*/ |
1263 |
|
|
event_dispatch(); |
1264 |
|
|
|
1265 |
|
|
return (0); |
1266 |
|
|
} |