1 /* $OpenBSD: npppctl.c,v 1.11 2023/02/21 15:45:40 mbuhl 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 imsg_init(&ctl_ibuf, ctlsock); 112 113 switch (result->action) { 114 case SESSION_BRIEF: 115 case SESSION_PKTS: 116 case SESSION_ALL: 117 show_clear_session(result, stdout); 118 break; 119 case CLEAR_SESSION: 120 if (!result->has_ppp_id) 121 show_clear_session(result, stdout); 122 else { 123 u_int ids[1]; 124 ids[0] = result->ppp_id; 125 clear_session(ids, 1, 1, stdout); 126 } 127 break; 128 case MONITOR_SESSION: 129 monitor_session(result, stdout); 130 break; 131 case NONE: 132 break; 133 } 134 135 exit(EXIT_SUCCESS); 136 } 137 138 static void 139 show_clear_session(struct parse_result *result, FILE *out) 140 { 141 int i, n, ppp_id_idx; 142 struct npppd_who_list *res; 143 u_int ppp_id[NMAX_DISCONNECT]; 144 145 if (imsg_compose(&ctl_ibuf, IMSG_CTL_WHO, 0, 0, -1, NULL, 0) == -1) 146 err(EXIT_FAILURE, "failed to compose a message\n"); 147 if (imsg_wait_command_completion() < 0) 148 errx(EXIT_FAILURE, "failed to get response"); 149 if (ctl_imsg.hdr.type != IMSG_CTL_OK) 150 errx(EXIT_FAILURE, "command was fail"); 151 n = ppp_id_idx = 0; 152 while (imsg_wait_command_completion() == IMSG_PPP_START) { 153 res = (struct npppd_who_list *)ctl_imsg.data; 154 if (ctl_imsg.hdr.len - IMSG_HEADER_SIZE < 155 offsetof(struct npppd_who_list, 156 entry[res->entry_count])) { 157 errx(1, "response size %d is too small for " 158 "the entry count %d", 159 (int)(ctl_imsg.hdr.len - IMSG_HEADER_SIZE), 160 res->entry_count); 161 } 162 for (i = 0; i < res->entry_count; i++, n++) { 163 switch (result->action) { 164 case SESSION_BRIEF: 165 fprint_who_brief(n, &res->entry[i], out); 166 break; 167 case SESSION_PKTS: 168 fprint_who_packets(n, &res->entry[i], out); 169 break; 170 case SESSION_ALL: 171 if (filter_match(result, &res->entry[i])) 172 fprint_who_all(n, &res->entry[i], out); 173 break; 174 case CLEAR_SESSION: 175 if (filter_match(result, &res->entry[i])) { 176 if (ppp_id_idx < nitems(ppp_id)) 177 ppp_id[ppp_id_idx] = 178 res->entry[i].ppp_id; 179 ppp_id_idx++; 180 } 181 break; 182 default: 183 warnx("must not reached here"); 184 abort(); 185 } 186 } 187 if (!res->more_data) 188 break; 189 } 190 if (result->action == CLEAR_SESSION) { 191 if (ppp_id_idx > nitems(ppp_id)) 192 warnx( 193 "Disconnection for %d sessions has been requested, " 194 "but cannot disconnect only %d sessions because of " 195 "the implementation limit.", 196 ppp_id_idx, (int)nitems(ppp_id)); 197 clear_session(ppp_id, MINIMUM(ppp_id_idx, nitems(ppp_id)), 198 ppp_id_idx, out); 199 } 200 } 201 202 const char *bar = 203 "------------------------------------------------------------------------\n"; 204 static void 205 monitor_session(struct parse_result *result, FILE *out) 206 { 207 int i, n; 208 struct npppd_who_list *res; 209 210 if (imsg_compose(&ctl_ibuf, IMSG_CTL_MONITOR, 0, 0, -1, NULL, 0) == -1) 211 err(EXIT_FAILURE, "failed to compose a message"); 212 if (imsg_wait_command_completion() < 0) 213 errx(EXIT_FAILURE, "failed to get response"); 214 if (ctl_imsg.hdr.type != IMSG_CTL_OK) 215 errx(EXIT_FAILURE, "command was fail"); 216 do { 217 if (imsg_wait_command_completion() < 0) 218 break; 219 n = 0; 220 if (ctl_imsg.hdr.type == IMSG_PPP_START || 221 ctl_imsg.hdr.type == IMSG_PPP_STOP) { 222 res = (struct npppd_who_list *)ctl_imsg.data; 223 for (i = 0; i < res->entry_count; i++) { 224 if (!filter_match(result, &res->entry[i])) 225 continue; 226 if (n == 0) 227 fprintf(out, "PPP %s\n%s", 228 (ctl_imsg.hdr.type == 229 IMSG_PPP_START) 230 ? "Started" 231 : "Stopped", bar); 232 fprint_who_all(n++, &res->entry[i], out); 233 } 234 if (n > 0) 235 fputs(bar, out); 236 } else { 237 warnx("received unknown message type = %d", 238 ctl_imsg.hdr.type); 239 break; 240 } 241 } while (true); 242 243 return; 244 } 245 246 static void 247 fprint_who_brief(int i, struct npppd_who *w, FILE *out) 248 { 249 char buf[BUFSIZ]; 250 251 if (i == 0) 252 fputs( 253 "Ppp Id Assigned IPv4 Username Proto Tunnel From\n" 254 "---------- --------------- -------------------- ----- ------------------------" 255 "-\n", 256 out); 257 fprintf(out, "%10u %-15s %-20s %-5s %s\n", w->ppp_id, 258 inet_ntoa(w->framed_ip_address), w->username, w->tunnel_proto, 259 peerstr((struct sockaddr *)&w->tunnel_peer, buf, sizeof(buf))); 260 } 261 262 static void 263 fprint_who_packets(int i, struct npppd_who *w, FILE *out) 264 { 265 if (i == 0) 266 fputs( 267 "Ppd Id Username In(Kbytes/pkts/errs) Out(Kbytes/pkts/errs)" 268 "\n" 269 "---------- -------------------- ----------------------- ----------------------" 270 "-\n", 271 out); 272 fprintf(out, "%10u %-20s %9.1f %7u %5u %9.1f %7u %5u\n", w->ppp_id, 273 w->username, 274 (double)w->ibytes/1024, w->ipackets, w->ierrors, 275 (double)w->obytes/1024, w->opackets, w->oerrors); 276 } 277 278 static void 279 fprint_who_all(int i, struct npppd_who *w, FILE *out) 280 { 281 struct tm tm; 282 char ibytes_buf[48], obytes_buf[48], peer_buf[48], time_buf[48]; 283 char dur_buf[48]; 284 285 localtime_r(&w->time, &tm); 286 strftime(time_buf, sizeof(time_buf), "%Y/%m/%d %T", &tm); 287 if (i != 0) 288 fputs("\n", out); 289 290 fprintf(out, 291 "Ppp Id = %u\n" 292 " Ppp Id : %u\n" 293 " Username : %s\n" 294 " Realm Name : %s\n" 295 " Concentrated Interface : %s\n" 296 " Assigned IPv4 Address : %s\n" 297 " MRU : %u\n" 298 " Tunnel Protocol : %s\n" 299 " Tunnel From : %s\n" 300 " Start Time : %s\n" 301 " Elapsed Time : %lu sec %s\n" 302 " Input Bytes : %llu%s\n" 303 " Input Packets : %lu\n" 304 " Input Errors : %lu (%.1f%%)\n" 305 " Output Bytes : %llu%s\n" 306 " Output Packets : %lu\n" 307 " Output Errors : %lu (%.1f%%)\n", 308 w->ppp_id, w->ppp_id, w->username, w->rlmname, w->ifname, 309 inet_ntoa(w->framed_ip_address), (u_int)w->mru, w->tunnel_proto, 310 peerstr((struct sockaddr *)&w->tunnel_peer, peer_buf, 311 sizeof(peer_buf)), time_buf, 312 (unsigned long)w->duration_sec, 313 humanize_duration(w->duration_sec, dur_buf, sizeof(dur_buf)), 314 (unsigned long long)w->ibytes, 315 humanize_bytes((double)w->ibytes, ibytes_buf, sizeof(ibytes_buf)), 316 (unsigned long)w->ipackets, 317 (unsigned long)w->ierrors, 318 ((w->ipackets + w->ierrors) <= 0) 319 ? 0.0 : (100.0 * w->ierrors) / (w->ierrors + w->ipackets), 320 (unsigned long long)w->obytes, 321 humanize_bytes((double)w->obytes, obytes_buf, sizeof(obytes_buf)), 322 (unsigned long)w->opackets, 323 (unsigned long)w->oerrors, 324 ((w->opackets + w->oerrors) <= 0) 325 ? 0.0 : (100.0 * w->oerrors) / (w->oerrors + w->opackets)); 326 } 327 328 /*********************************************************************** 329 * clear session 330 ***********************************************************************/ 331 static void 332 clear_session(u_int ppp_id[], int ppp_id_count, int total, FILE *out) 333 { 334 int succ, fail, i, n, nmax; 335 struct iovec iov[2]; 336 struct npppd_disconnect_request req; 337 struct npppd_disconnect_response *res; 338 339 succ = fail = 0; 340 if (ppp_id_count > 0) { 341 nmax = (MAX_IMSGSIZE - IMSG_HEADER_SIZE - 342 offsetof(struct npppd_disconnect_request, ppp_id[0])) / 343 sizeof(u_int); 344 for (i = 0; i < ppp_id_count; i += n) { 345 n = MINIMUM(nmax, ppp_id_count - i); 346 req.count = n; 347 iov[0].iov_base = &req; 348 iov[0].iov_len = offsetof( 349 struct npppd_disconnect_request, ppp_id[0]); 350 iov[1].iov_base = &ppp_id[i]; 351 iov[1].iov_len = sizeof(u_int) * n; 352 353 if (imsg_composev(&ctl_ibuf, IMSG_CTL_DISCONNECT, 0, 0, 354 -1, iov, 2) == -1) 355 err(EXIT_FAILURE, 356 "Failed to compose a message"); 357 if (imsg_wait_command_completion() < 0) 358 errx(EXIT_FAILURE, "failed to get response"); 359 if (ctl_imsg.hdr.type != IMSG_CTL_OK) 360 errx(EXIT_FAILURE, 361 "Command was fail: msg type = %d", 362 ctl_imsg.hdr.type); 363 if (ctl_imsg.hdr.len - IMSG_HEADER_SIZE < 364 sizeof(struct npppd_disconnect_response)) 365 err(EXIT_FAILURE, "response is corrupted"); 366 res = (struct npppd_disconnect_response *)ctl_imsg.data; 367 succ += res->count; 368 } 369 fail = total - succ; 370 } 371 if (succ > 0) 372 fprintf(out, "Successfully disconnected %d session%s.\n", 373 succ, (succ > 1)? "s" : ""); 374 if (fail > 0) 375 fprintf(out, "Failed to disconnect %d session%s.\n", 376 fail, (fail > 1)? "s" : ""); 377 if (succ == 0 && fail == 0) 378 fprintf(out, "No session to disconnect.\n"); 379 } 380 381 /*********************************************************************** 382 * common functions 383 ***********************************************************************/ 384 static bool 385 filter_match(struct parse_result *result, struct npppd_who *who) 386 { 387 if (result->has_ppp_id && result->ppp_id != who->ppp_id) 388 return (false); 389 390 switch (result->address.ss_family) { 391 case AF_INET: 392 if (((struct sockaddr_in *)&result->address)->sin_addr. 393 s_addr != who->framed_ip_address.s_addr) 394 return (false); 395 break; 396 case AF_INET6: 397 /* npppd doesn't support IPv6 yet */ 398 return (false); 399 } 400 401 if (result->interface != NULL && 402 strcmp(result->interface, who->ifname) != 0) 403 return (false); 404 405 if (result->protocol != PROTO_UNSPEC && 406 result->protocol != parse_protocol(who->tunnel_proto) ) 407 return (false); 408 409 if (result->realm != NULL && strcmp(result->realm, who->rlmname) != 0) 410 return (false); 411 412 if (result->username != NULL && 413 strcmp(result->username, who->username) != 0) 414 return (false); 415 416 return (true); 417 } 418 419 static const char * 420 peerstr(struct sockaddr *sa, char *buf, int lbuf) 421 { 422 int niflags, hasserv; 423 char hoststr[NI_MAXHOST], servstr[NI_MAXSERV]; 424 425 niflags = hasserv = 0; 426 if (nflag) 427 niflags |= NI_NUMERICHOST; 428 if (sa->sa_family == AF_INET || sa->sa_family ==AF_INET6) { 429 hasserv = 1; 430 niflags |= NI_NUMERICSERV; 431 } 432 433 if (sa->sa_family == AF_LINK) 434 snprintf(hoststr, sizeof(hoststr), 435 "%02x:%02x:%02x:%02x:%02x:%02x", 436 LLADDR((struct sockaddr_dl *)sa)[0] & 0xff, 437 LLADDR((struct sockaddr_dl *)sa)[1] & 0xff, 438 LLADDR((struct sockaddr_dl *)sa)[2] & 0xff, 439 LLADDR((struct sockaddr_dl *)sa)[3] & 0xff, 440 LLADDR((struct sockaddr_dl *)sa)[4] & 0xff, 441 LLADDR((struct sockaddr_dl *)sa)[5] & 0xff); 442 else 443 getnameinfo(sa, sa->sa_len, hoststr, sizeof(hoststr), servstr, 444 sizeof(servstr), niflags); 445 446 strlcpy(buf, hoststr, lbuf); 447 if (hasserv) { 448 strlcat(buf, ":", lbuf); 449 strlcat(buf, servstr, lbuf); 450 } 451 452 return (buf); 453 } 454 455 static const char * 456 humanize_duration(uint32_t sec, char *buf, int lbuf) 457 { 458 char fbuf[128]; 459 int hour, min; 460 461 hour = sec / (60 * 60); 462 min = sec / 60; 463 min %= 60; 464 465 if (lbuf <= 0) 466 return (buf); 467 buf[0] = '\0'; 468 if (hour || min) { 469 strlcat(buf, "(", lbuf); 470 if (hour) { 471 snprintf(fbuf, sizeof(fbuf), 472 "%d hour%s", hour, (hour > 1)? "s" : ""); 473 strlcat(buf, fbuf, lbuf); 474 } 475 if (hour && min) 476 strlcat(buf, " and ", lbuf); 477 if (min) { 478 snprintf(fbuf, sizeof(fbuf), 479 "%d minute%s", min, (min > 1)? "s" : ""); 480 strlcat(buf, fbuf, lbuf); 481 } 482 strlcat(buf, ")", lbuf); 483 } 484 485 return (buf); 486 } 487 488 static const char * 489 humanize_bytes(double val, char *buf, int lbuf) 490 { 491 if (lbuf <= 0) 492 return (buf); 493 494 if (val >= 1000 * 1024 * 1024) 495 snprintf(buf, lbuf, " (%.1f GB)", 496 (double)val / (1024 * 1024 * 1024)); 497 else if (val >= 1000 * 1024) 498 snprintf(buf, lbuf, " (%.1f MB)", (double)val / (1024 * 1024)); 499 else if (val >= 1000) 500 snprintf(buf, lbuf, " (%.1f KB)", (double)val / 1024); 501 else 502 buf[0] = '\0'; 503 504 return (buf); 505 } 506 507 static int 508 imsg_wait_command_completion(void) 509 { 510 int n; 511 512 while (ctl_ibuf.w.queued) 513 if (msgbuf_write(&ctl_ibuf.w) <= 0 && errno != EAGAIN) 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 (((n = imsg_read(&ctl_ibuf)) == -1 && errno != EAGAIN) || 521 n == 0) 522 return (-1); 523 } while (1); 524 525 return (ctl_imsg.hdr.type); 526 } 527