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 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 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 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 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 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 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 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 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 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 * 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 * 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 * 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 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