1 /* $OpenBSD: npppctl.c,v 1.4 2014/07/22 02:02:59 yasuoka 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/param.h> 19 #include <sys/queue.h> 20 #include <sys/socket.h> 21 #include <sys/un.h> 22 #include <sys/uio.h> 23 #include <net/if.h> 24 #include <net/if_dl.h> 25 #include <netinet/in.h> 26 #include <arpa/inet.h> 27 28 #include <errno.h> 29 #include <netdb.h> 30 #include <stdbool.h> 31 #include <stddef.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <imsg.h> 36 37 #include <unistd.h> 38 #include <err.h> 39 40 #include "parser.h" 41 #include "npppd_ctl.h" 42 43 #ifndef nitems 44 #define nitems(_x) (sizeof(_x) / sizeof(_x[0])) 45 #endif 46 47 #define NMAX_DISCONNECT 2048 48 49 static void usage (void); 50 static void show_clear_session (struct parse_result *, FILE *); 51 static void monitor_session (struct parse_result *, FILE *); 52 static void clear_session (u_int[], int, int, FILE *); 53 static void fprint_who_brief (int, struct npppd_who *, FILE *); 54 static void fprint_who_packets (int, struct npppd_who *, FILE *); 55 static void fprint_who_all (int, struct npppd_who *, FILE *); 56 static const char *peerstr (struct sockaddr *, char *, int); 57 static const char *humanize_duration (uint32_t, char *, int); 58 static const char *humanize_bytes (double, char *, int); 59 static bool filter_match(struct parse_result *, struct npppd_who *); 60 static int imsg_wait_command_completion (void); 61 62 static int nflag = 0; 63 static struct imsgbuf ctl_ibuf; 64 static struct imsg ctl_imsg; 65 66 static void 67 usage(void) 68 { 69 extern char *__progname; 70 71 fprintf(stderr, 72 "usage: %s [-n] [-s socket] command [arg ...]\n", __progname); 73 } 74 75 int 76 main(int argc, char *argv[]) 77 { 78 int ch, ctlsock = -1; 79 struct parse_result *result; 80 struct sockaddr_un sun; 81 const char *npppd_ctlpath = NPPPD_SOCKET; 82 extern int optind; 83 extern char *optarg; 84 85 while ((ch = getopt(argc, argv, "ns:")) != -1) 86 switch (ch) { 87 case 'n': 88 nflag = 1; 89 break; 90 case 's': 91 npppd_ctlpath = optarg; 92 break; 93 default: 94 usage(); 95 exit(EXIT_FAILURE); 96 } 97 98 argc -= optind; 99 argv += optind; 100 101 if ((result = parse(argc, argv)) == NULL) 102 exit(EXIT_FAILURE); 103 104 if ((ctlsock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) 105 err(EXIT_FAILURE, "socket"); 106 memset(&sun, 0, sizeof(sun)); 107 sun.sun_family = AF_UNIX; 108 sun.sun_len = sizeof(sun); 109 strlcpy(sun.sun_path, npppd_ctlpath, sizeof(sun.sun_path)); 110 if (connect(ctlsock, (struct sockaddr *)&sun, sizeof(sun)) < 0) 111 err(EXIT_FAILURE, "connect"); 112 113 imsg_init(&ctl_ibuf, ctlsock); 114 115 switch (result->action) { 116 case SESSION_BRIEF: 117 case SESSION_PKTS: 118 case SESSION_ALL: 119 show_clear_session(result, stdout); 120 break; 121 case CLEAR_SESSION: 122 if (!result->has_ppp_id) 123 show_clear_session(result, stdout); 124 else { 125 u_int ids[1]; 126 ids[0] = result->ppp_id; 127 clear_session(ids, 1, 1, stdout); 128 } 129 break; 130 case MONITOR_SESSION: 131 monitor_session(result, stdout); 132 break; 133 case NONE: 134 break; 135 } 136 137 exit(EXIT_SUCCESS); 138 } 139 140 static void 141 show_clear_session(struct parse_result *result, FILE *out) 142 { 143 int i, n, ppp_id_idx; 144 struct npppd_who_list *res; 145 u_int ppp_id[NMAX_DISCONNECT]; 146 147 if (imsg_compose(&ctl_ibuf, IMSG_CTL_WHO, 0, 0, -1, NULL, 0) == -1) 148 err(EXIT_FAILURE, "failed to componse a message\n"); 149 if (imsg_wait_command_completion() < 0) 150 errx(EXIT_FAILURE, "failed to get response"); 151 if (ctl_imsg.hdr.type != IMSG_CTL_OK) 152 errx(EXIT_FAILURE, "command was fail"); 153 n = ppp_id_idx = 0; 154 while (imsg_wait_command_completion() == IMSG_PPP_START) { 155 res = (struct npppd_who_list *)ctl_imsg.data; 156 if (ctl_imsg.hdr.len - IMSG_HEADER_SIZE < 157 offsetof(struct npppd_who_list, 158 entry[res->entry_count])) { 159 errx(1, "response size %d is too small for " 160 "the entry count %d", 161 (int)(ctl_imsg.hdr.len - IMSG_HEADER_SIZE), 162 res->entry_count); 163 } 164 for (i = 0; i < res->entry_count; i++, n++) { 165 switch (result->action) { 166 case SESSION_BRIEF: 167 fprint_who_brief(n, &res->entry[i], out); 168 break; 169 case SESSION_PKTS: 170 fprint_who_packets(n, &res->entry[i], out); 171 break; 172 case SESSION_ALL: 173 if (filter_match(result, &res->entry[i])) 174 fprint_who_all(n, &res->entry[i], out); 175 break; 176 case CLEAR_SESSION: 177 if (filter_match(result, &res->entry[i])) { 178 if (ppp_id_idx < nitems(ppp_id)) 179 ppp_id[ppp_id_idx] = 180 res->entry[i].ppp_id; 181 ppp_id_idx++; 182 } 183 break; 184 default: 185 warnx("must not reached here"); 186 abort(); 187 } 188 } 189 if (!res->more_data) 190 break; 191 } 192 if (result->action == CLEAR_SESSION) { 193 if (ppp_id_idx > nitems(ppp_id)) 194 warnx( 195 "Disconnection for %d sessions has been requested, " 196 "but cannot disconnect only %d sessions because of " 197 "the implementation limit.", 198 ppp_id_idx, (int)nitems(ppp_id)); 199 clear_session(ppp_id, MIN(ppp_id_idx, nitems(ppp_id)), 200 ppp_id_idx, out); 201 } 202 } 203 204 const char *bar = 205 "------------------------------------------------------------------------\n"; 206 static void 207 monitor_session(struct parse_result *result, FILE *out) 208 { 209 int i, n; 210 struct npppd_who_list *res; 211 212 if (imsg_compose(&ctl_ibuf, IMSG_CTL_MONITOR, 0, 0, -1, NULL, 0) == -1) 213 err(EXIT_FAILURE, "failed to compose a message"); 214 if (imsg_wait_command_completion() < 0) 215 errx(EXIT_FAILURE, "failed to get response"); 216 if (ctl_imsg.hdr.type != IMSG_CTL_OK) 217 errx(EXIT_FAILURE, "command was fail"); 218 do { 219 if (imsg_wait_command_completion() < 0) 220 break; 221 n = 0; 222 if (ctl_imsg.hdr.type == IMSG_PPP_START || 223 ctl_imsg.hdr.type == IMSG_PPP_STOP) { 224 res = (struct npppd_who_list *)ctl_imsg.data; 225 for (i = 0; i < res->entry_count; i++) { 226 if (!filter_match(result, &res->entry[i])) 227 continue; 228 if (n == 0) 229 fprintf(out, "PPP %s\n%s", 230 (ctl_imsg.hdr.type == 231 IMSG_PPP_START) 232 ? "Started" 233 : "Stopped", bar); 234 fprint_who_all(n++, &res->entry[i], out); 235 } 236 if (n > 0) 237 fputs(bar, out); 238 } else { 239 warnx("received unknown message type = %d", 240 ctl_imsg.hdr.type); 241 break; 242 } 243 } while (true); 244 245 return; 246 } 247 248 static void 249 fprint_who_brief(int i, struct npppd_who *w, FILE *out) 250 { 251 char buf[BUFSIZ]; 252 253 if (i == 0) 254 fputs( 255 "Ppp Id Assigned IPv4 Username Proto Tunnel From\n" 256 "---------- --------------- -------------------- ----- ------------------------" 257 "-\n", 258 out); 259 fprintf(out, "%10u %-15s %-20s %-5s %s\n", w->ppp_id, 260 inet_ntoa(w->framed_ip_address), w->username, w->tunnel_proto, 261 peerstr((struct sockaddr *)&w->tunnel_peer, buf, sizeof(buf))); 262 } 263 264 static void 265 fprint_who_packets(int i, struct npppd_who *w, FILE *out) 266 { 267 if (i == 0) 268 fputs( 269 "Ppd Id Username In(Kbytes/pkts/errs) Out(Kbytes/pkts/errs)" 270 "\n" 271 "---------- -------------------- ----------------------- ----------------------" 272 "-\n", 273 out); 274 fprintf(out, "%10u %-20s %9.1f %7u %5u %9.1f %7u %5u\n", w->ppp_id, 275 w->username, 276 (double)w->ibytes/1024, w->ipackets, w->ierrors, 277 (double)w->obytes/1024, w->opackets, w->oerrors); 278 } 279 280 static void 281 fprint_who_all(int i, struct npppd_who *w, FILE *out) 282 { 283 struct tm tm; 284 char ibytes_buf[48], obytes_buf[48], peer_buf[48], time_buf[48]; 285 char dur_buf[48]; 286 287 localtime_r(&w->time, &tm); 288 strftime(time_buf, sizeof(time_buf), "%Y/%m/%d %T", &tm); 289 if (i != 0) 290 fputs("\n", out); 291 292 fprintf(out, 293 "Ppp Id = %u\n" 294 " Ppp Id : %u\n" 295 " Username : %s\n" 296 " Realm Name : %s\n" 297 " Concentrated Interface : %s\n" 298 " Assigned IPv4 Address : %s\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), 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 = MIN(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 while (ctl_ibuf.w.queued) 514 if (msgbuf_write(&ctl_ibuf.w) <= 0 && errno != EAGAIN) 515 return (-1); 516 do { 517 if ((n = imsg_get(&ctl_ibuf, &ctl_imsg)) == -1) 518 return (-1); 519 if (n != 0) 520 break; 521 if ((n = imsg_read(&ctl_ibuf)) == -1 || n == 0) 522 return (-1); 523 } while (1); 524 525 return (ctl_imsg.hdr.type); 526 } 527