1 /* $OpenBSD: npppctl.c,v 1.15 2024/11/21 13:43:10 claudio Exp $ */
2
3 /*
4 * Copyright (c) 2012 Internet Initiative Japan Inc.
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 #include <sys/queue.h>
19 #include <sys/socket.h>
20 #include <sys/un.h>
21 #include <sys/uio.h>
22 #include <net/if.h>
23 #include <net/if_dl.h>
24 #include <netinet/in.h>
25 #include <arpa/inet.h>
26
27 #include <errno.h>
28 #include <netdb.h>
29 #include <stdbool.h>
30 #include <stddef.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <imsg.h>
35
36 #include <unistd.h>
37 #include <err.h>
38
39 #include "parser.h"
40 #include "npppd_ctl.h"
41
42 #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
43
44 #ifndef nitems
45 #define nitems(_x) (sizeof(_x) / sizeof(_x[0]))
46 #endif
47
48 #define NMAX_DISCONNECT 2048
49
50 static void usage (void);
51 static void show_clear_session (struct parse_result *, FILE *);
52 static void monitor_session (struct parse_result *, FILE *);
53 static void clear_session (u_int[], int, int, FILE *);
54 static void fprint_who_brief (int, struct npppd_who *, FILE *);
55 static void fprint_who_packets (int, struct npppd_who *, FILE *);
56 static void fprint_who_all (int, struct npppd_who *, FILE *);
57 static const char *peerstr (struct sockaddr *, char *, int);
58 static const char *humanize_duration (uint32_t, char *, int);
59 static const char *humanize_bytes (double, char *, int);
60 static bool filter_match(struct parse_result *, struct npppd_who *);
61 static int imsg_wait_command_completion (void);
62
63 static int nflag = 0;
64 static struct imsgbuf ctl_ibuf;
65 static struct imsg ctl_imsg;
66
67 static void
usage(void)68 usage(void)
69 {
70 extern char *__progname;
71
72 fprintf(stderr,
73 "usage: %s [-n] [-s socket] command [arg ...]\n", __progname);
74 }
75
76 int
main(int argc,char * argv[])77 main(int argc, char *argv[])
78 {
79 int ch, ctlsock = -1;
80 struct parse_result *result;
81 struct sockaddr_un sun;
82 const char *npppd_ctlpath = NPPPD_SOCKET;
83
84 while ((ch = getopt(argc, argv, "ns:")) != -1)
85 switch (ch) {
86 case 'n':
87 nflag = 1;
88 break;
89 case 's':
90 npppd_ctlpath = optarg;
91 break;
92 default:
93 usage();
94 exit(EXIT_FAILURE);
95 }
96
97 argc -= optind;
98 argv += optind;
99
100 if ((result = parse(argc, argv)) == NULL)
101 exit(EXIT_FAILURE);
102
103 if ((ctlsock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
104 err(EXIT_FAILURE, "socket");
105 memset(&sun, 0, sizeof(sun));
106 sun.sun_family = AF_UNIX;
107 strlcpy(sun.sun_path, npppd_ctlpath, sizeof(sun.sun_path));
108 if (connect(ctlsock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
109 err(EXIT_FAILURE, "connect");
110
111 if (imsgbuf_init(&ctl_ibuf, ctlsock) == -1)
112 err(EXIT_FAILURE, "imsgbuf_init");
113
114 switch (result->action) {
115 case SESSION_BRIEF:
116 case SESSION_PKTS:
117 case SESSION_ALL:
118 show_clear_session(result, stdout);
119 break;
120 case CLEAR_SESSION:
121 if (!result->has_ppp_id)
122 show_clear_session(result, stdout);
123 else {
124 u_int ids[1];
125 ids[0] = result->ppp_id;
126 clear_session(ids, 1, 1, stdout);
127 }
128 break;
129 case MONITOR_SESSION:
130 monitor_session(result, stdout);
131 break;
132 case NONE:
133 break;
134 }
135
136 exit(EXIT_SUCCESS);
137 }
138
139 static void
show_clear_session(struct parse_result * result,FILE * out)140 show_clear_session(struct parse_result *result, FILE *out)
141 {
142 int i, n, ppp_id_idx;
143 struct npppd_who_list *res;
144 u_int ppp_id[NMAX_DISCONNECT];
145
146 if (imsg_compose(&ctl_ibuf, IMSG_CTL_WHO, 0, 0, -1, NULL, 0) == -1)
147 err(EXIT_FAILURE, "failed to compose a message\n");
148 if (imsg_wait_command_completion() < 0)
149 errx(EXIT_FAILURE, "failed to get response");
150 if (ctl_imsg.hdr.type != IMSG_CTL_OK)
151 errx(EXIT_FAILURE, "command was fail");
152 n = ppp_id_idx = 0;
153 while (imsg_wait_command_completion() == IMSG_PPP_START) {
154 res = (struct npppd_who_list *)ctl_imsg.data;
155 if (ctl_imsg.hdr.len - IMSG_HEADER_SIZE <
156 offsetof(struct npppd_who_list,
157 entry[res->entry_count])) {
158 errx(1, "response size %d is too small for "
159 "the entry count %d",
160 (int)(ctl_imsg.hdr.len - IMSG_HEADER_SIZE),
161 res->entry_count);
162 }
163 for (i = 0; i < res->entry_count; i++, n++) {
164 switch (result->action) {
165 case SESSION_BRIEF:
166 fprint_who_brief(n, &res->entry[i], out);
167 break;
168 case SESSION_PKTS:
169 fprint_who_packets(n, &res->entry[i], out);
170 break;
171 case SESSION_ALL:
172 if (filter_match(result, &res->entry[i]))
173 fprint_who_all(n, &res->entry[i], out);
174 break;
175 case CLEAR_SESSION:
176 if (filter_match(result, &res->entry[i])) {
177 if (ppp_id_idx < nitems(ppp_id))
178 ppp_id[ppp_id_idx] =
179 res->entry[i].ppp_id;
180 ppp_id_idx++;
181 }
182 break;
183 default:
184 warnx("must not reached here");
185 abort();
186 }
187 }
188 if (!res->more_data)
189 break;
190 }
191 if (result->action == CLEAR_SESSION) {
192 if (ppp_id_idx > nitems(ppp_id))
193 warnx(
194 "Disconnection for %d sessions has been requested, "
195 "but cannot disconnect only %d sessions because of "
196 "the implementation limit.",
197 ppp_id_idx, (int)nitems(ppp_id));
198 clear_session(ppp_id, MINIMUM(ppp_id_idx, nitems(ppp_id)),
199 ppp_id_idx, out);
200 }
201 }
202
203 const char *bar =
204 "------------------------------------------------------------------------\n";
205 static void
monitor_session(struct parse_result * result,FILE * out)206 monitor_session(struct parse_result *result, FILE *out)
207 {
208 int i, n;
209 struct npppd_who_list *res;
210
211 if (imsg_compose(&ctl_ibuf, IMSG_CTL_MONITOR, 0, 0, -1, NULL, 0) == -1)
212 err(EXIT_FAILURE, "failed to compose a message");
213 if (imsg_wait_command_completion() < 0)
214 errx(EXIT_FAILURE, "failed to get response");
215 if (ctl_imsg.hdr.type != IMSG_CTL_OK)
216 errx(EXIT_FAILURE, "command was fail");
217 do {
218 if (imsg_wait_command_completion() < 0)
219 break;
220 n = 0;
221 if (ctl_imsg.hdr.type == IMSG_PPP_START ||
222 ctl_imsg.hdr.type == IMSG_PPP_STOP) {
223 res = (struct npppd_who_list *)ctl_imsg.data;
224 for (i = 0; i < res->entry_count; i++) {
225 if (!filter_match(result, &res->entry[i]))
226 continue;
227 if (n == 0)
228 fprintf(out, "PPP %s\n%s",
229 (ctl_imsg.hdr.type ==
230 IMSG_PPP_START)
231 ? "Started"
232 : "Stopped", bar);
233 fprint_who_all(n++, &res->entry[i], out);
234 }
235 if (n > 0)
236 fputs(bar, out);
237 } else {
238 warnx("received unknown message type = %d",
239 ctl_imsg.hdr.type);
240 break;
241 }
242 } while (true);
243
244 return;
245 }
246
247 static void
fprint_who_brief(int i,struct npppd_who * w,FILE * out)248 fprint_who_brief(int i, struct npppd_who *w, FILE *out)
249 {
250 char buf[BUFSIZ];
251
252 if (i == 0)
253 fputs(
254 "Ppp Id Assigned IPv4 Username Proto Tunnel From\n"
255 "---------- --------------- -------------------- ----- ------------------------"
256 "-\n",
257 out);
258 fprintf(out, "%10u %-15s %-20s %-5s %s\n", w->ppp_id,
259 inet_ntoa(w->framed_ip_address), w->username, w->tunnel_proto,
260 peerstr((struct sockaddr *)&w->tunnel_peer, buf, sizeof(buf)));
261 }
262
263 static void
fprint_who_packets(int i,struct npppd_who * w,FILE * out)264 fprint_who_packets(int i, struct npppd_who *w, FILE *out)
265 {
266 if (i == 0)
267 fputs(
268 "Ppd Id Username In(Kbytes/pkts/errs) Out(Kbytes/pkts/errs)"
269 "\n"
270 "---------- -------------------- ----------------------- ----------------------"
271 "-\n",
272 out);
273 fprintf(out, "%10u %-20s %9.1f %7u %5u %9.1f %7u %5u\n", w->ppp_id,
274 w->username,
275 (double)w->ibytes/1024, w->ipackets, w->ierrors,
276 (double)w->obytes/1024, w->opackets, w->oerrors);
277 }
278
279 static void
fprint_who_all(int i,struct npppd_who * w,FILE * out)280 fprint_who_all(int i, struct npppd_who *w, FILE *out)
281 {
282 struct tm tm;
283 char ibytes_buf[48], obytes_buf[48], peer_buf[48], time_buf[48];
284 char dur_buf[48];
285
286 localtime_r(&w->time, &tm);
287 strftime(time_buf, sizeof(time_buf), "%Y/%m/%d %T", &tm);
288 if (i != 0)
289 fputs("\n", out);
290
291 fprintf(out,
292 "Ppp Id = %u\n"
293 " Ppp Id : %u\n"
294 " Username : %s\n"
295 " Realm Name : %s\n"
296 " Concentrated Interface : %s\n"
297 " Assigned IPv4 Address : %s\n"
298 " MRU : %u\n"
299 " Tunnel Protocol : %s\n"
300 " Tunnel From : %s\n"
301 " Start Time : %s\n"
302 " Elapsed Time : %lu sec %s\n"
303 " Input Bytes : %llu%s\n"
304 " Input Packets : %lu\n"
305 " Input Errors : %lu (%.1f%%)\n"
306 " Output Bytes : %llu%s\n"
307 " Output Packets : %lu\n"
308 " Output Errors : %lu (%.1f%%)\n",
309 w->ppp_id, w->ppp_id, w->username, w->rlmname, w->ifname,
310 inet_ntoa(w->framed_ip_address), (u_int)w->mru, w->tunnel_proto,
311 peerstr((struct sockaddr *)&w->tunnel_peer, peer_buf,
312 sizeof(peer_buf)), time_buf,
313 (unsigned long)w->duration_sec,
314 humanize_duration(w->duration_sec, dur_buf, sizeof(dur_buf)),
315 (unsigned long long)w->ibytes,
316 humanize_bytes((double)w->ibytes, ibytes_buf, sizeof(ibytes_buf)),
317 (unsigned long)w->ipackets,
318 (unsigned long)w->ierrors,
319 ((w->ipackets + w->ierrors) <= 0)
320 ? 0.0 : (100.0 * w->ierrors) / (w->ierrors + w->ipackets),
321 (unsigned long long)w->obytes,
322 humanize_bytes((double)w->obytes, obytes_buf, sizeof(obytes_buf)),
323 (unsigned long)w->opackets,
324 (unsigned long)w->oerrors,
325 ((w->opackets + w->oerrors) <= 0)
326 ? 0.0 : (100.0 * w->oerrors) / (w->oerrors + w->opackets));
327 }
328
329 /***********************************************************************
330 * clear session
331 ***********************************************************************/
332 static void
clear_session(u_int ppp_id[],int ppp_id_count,int total,FILE * out)333 clear_session(u_int ppp_id[], int ppp_id_count, int total, FILE *out)
334 {
335 int succ, fail, i, n, nmax;
336 struct iovec iov[2];
337 struct npppd_disconnect_request req;
338 struct npppd_disconnect_response *res;
339
340 succ = fail = 0;
341 if (ppp_id_count > 0) {
342 nmax = (MAX_IMSGSIZE - IMSG_HEADER_SIZE -
343 offsetof(struct npppd_disconnect_request, ppp_id[0])) /
344 sizeof(u_int);
345 for (i = 0; i < ppp_id_count; i += n) {
346 n = MINIMUM(nmax, ppp_id_count - i);
347 req.count = n;
348 iov[0].iov_base = &req;
349 iov[0].iov_len = offsetof(
350 struct npppd_disconnect_request, ppp_id[0]);
351 iov[1].iov_base = &ppp_id[i];
352 iov[1].iov_len = sizeof(u_int) * n;
353
354 if (imsg_composev(&ctl_ibuf, IMSG_CTL_DISCONNECT, 0, 0,
355 -1, iov, 2) == -1)
356 err(EXIT_FAILURE,
357 "Failed to compose a message");
358 if (imsg_wait_command_completion() < 0)
359 errx(EXIT_FAILURE, "failed to get response");
360 if (ctl_imsg.hdr.type != IMSG_CTL_OK)
361 errx(EXIT_FAILURE,
362 "Command was fail: msg type = %d",
363 ctl_imsg.hdr.type);
364 if (ctl_imsg.hdr.len - IMSG_HEADER_SIZE <
365 sizeof(struct npppd_disconnect_response))
366 err(EXIT_FAILURE, "response is corrupted");
367 res = (struct npppd_disconnect_response *)ctl_imsg.data;
368 succ += res->count;
369 }
370 fail = total - succ;
371 }
372 if (succ > 0)
373 fprintf(out, "Successfully disconnected %d session%s.\n",
374 succ, (succ > 1)? "s" : "");
375 if (fail > 0)
376 fprintf(out, "Failed to disconnect %d session%s.\n",
377 fail, (fail > 1)? "s" : "");
378 if (succ == 0 && fail == 0)
379 fprintf(out, "No session to disconnect.\n");
380 }
381
382 /***********************************************************************
383 * common functions
384 ***********************************************************************/
385 static bool
filter_match(struct parse_result * result,struct npppd_who * who)386 filter_match(struct parse_result *result, struct npppd_who *who)
387 {
388 if (result->has_ppp_id && result->ppp_id != who->ppp_id)
389 return (false);
390
391 switch (result->address.ss_family) {
392 case AF_INET:
393 if (((struct sockaddr_in *)&result->address)->sin_addr.
394 s_addr != who->framed_ip_address.s_addr)
395 return (false);
396 break;
397 case AF_INET6:
398 /* npppd doesn't support IPv6 yet */
399 return (false);
400 }
401
402 if (result->interface != NULL &&
403 strcmp(result->interface, who->ifname) != 0)
404 return (false);
405
406 if (result->protocol != PROTO_UNSPEC &&
407 result->protocol != parse_protocol(who->tunnel_proto) )
408 return (false);
409
410 if (result->realm != NULL && strcmp(result->realm, who->rlmname) != 0)
411 return (false);
412
413 if (result->username != NULL &&
414 strcmp(result->username, who->username) != 0)
415 return (false);
416
417 return (true);
418 }
419
420 static const char *
peerstr(struct sockaddr * sa,char * buf,int lbuf)421 peerstr(struct sockaddr *sa, char *buf, int lbuf)
422 {
423 int niflags, hasserv;
424 char hoststr[NI_MAXHOST], servstr[NI_MAXSERV];
425
426 niflags = hasserv = 0;
427 if (nflag)
428 niflags |= NI_NUMERICHOST;
429 if (sa->sa_family == AF_INET || sa->sa_family ==AF_INET6) {
430 hasserv = 1;
431 niflags |= NI_NUMERICSERV;
432 }
433
434 if (sa->sa_family == AF_LINK)
435 snprintf(hoststr, sizeof(hoststr),
436 "%02x:%02x:%02x:%02x:%02x:%02x",
437 LLADDR((struct sockaddr_dl *)sa)[0] & 0xff,
438 LLADDR((struct sockaddr_dl *)sa)[1] & 0xff,
439 LLADDR((struct sockaddr_dl *)sa)[2] & 0xff,
440 LLADDR((struct sockaddr_dl *)sa)[3] & 0xff,
441 LLADDR((struct sockaddr_dl *)sa)[4] & 0xff,
442 LLADDR((struct sockaddr_dl *)sa)[5] & 0xff);
443 else
444 getnameinfo(sa, sa->sa_len, hoststr, sizeof(hoststr), servstr,
445 sizeof(servstr), niflags);
446
447 strlcpy(buf, hoststr, lbuf);
448 if (hasserv) {
449 strlcat(buf, ":", lbuf);
450 strlcat(buf, servstr, lbuf);
451 }
452
453 return (buf);
454 }
455
456 static const char *
humanize_duration(uint32_t sec,char * buf,int lbuf)457 humanize_duration(uint32_t sec, char *buf, int lbuf)
458 {
459 char fbuf[128];
460 int hour, min;
461
462 hour = sec / (60 * 60);
463 min = sec / 60;
464 min %= 60;
465
466 if (lbuf <= 0)
467 return (buf);
468 buf[0] = '\0';
469 if (hour || min) {
470 strlcat(buf, "(", lbuf);
471 if (hour) {
472 snprintf(fbuf, sizeof(fbuf),
473 "%d hour%s", hour, (hour > 1)? "s" : "");
474 strlcat(buf, fbuf, lbuf);
475 }
476 if (hour && min)
477 strlcat(buf, " and ", lbuf);
478 if (min) {
479 snprintf(fbuf, sizeof(fbuf),
480 "%d minute%s", min, (min > 1)? "s" : "");
481 strlcat(buf, fbuf, lbuf);
482 }
483 strlcat(buf, ")", lbuf);
484 }
485
486 return (buf);
487 }
488
489 static const char *
humanize_bytes(double val,char * buf,int lbuf)490 humanize_bytes(double val, char *buf, int lbuf)
491 {
492 if (lbuf <= 0)
493 return (buf);
494
495 if (val >= 1000 * 1024 * 1024)
496 snprintf(buf, lbuf, " (%.1f GB)",
497 (double)val / (1024 * 1024 * 1024));
498 else if (val >= 1000 * 1024)
499 snprintf(buf, lbuf, " (%.1f MB)", (double)val / (1024 * 1024));
500 else if (val >= 1000)
501 snprintf(buf, lbuf, " (%.1f KB)", (double)val / 1024);
502 else
503 buf[0] = '\0';
504
505 return (buf);
506 }
507
508 static int
imsg_wait_command_completion(void)509 imsg_wait_command_completion(void)
510 {
511 int n;
512
513 if (imsgbuf_flush(&ctl_ibuf) == -1)
514 return (-1);
515 do {
516 if ((n = imsg_get(&ctl_ibuf, &ctl_imsg)) == -1)
517 return (-1);
518 if (n != 0)
519 break;
520 if (imsgbuf_read(&ctl_ibuf) != 1)
521 return (-1);
522 } while (1);
523
524 return (ctl_imsg.hdr.type);
525 }
526