1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <unistd.h> 29 #include <sys/types.h> 30 #include <sys/stat.h> 31 #include <stdlib.h> 32 #include <netinet/in.h> /* struct in_addr */ 33 #include <netinet/dhcp.h> 34 #include <signal.h> 35 #include <sys/socket.h> 36 #include <net/route.h> 37 #include <net/if_arp.h> 38 #include <string.h> 39 #include <dhcpmsg.h> 40 #include <ctype.h> 41 #include <netdb.h> 42 #include <fcntl.h> 43 #include <stdio.h> 44 45 #include "states.h" 46 #include "agent.h" 47 #include "interface.h" 48 #include "util.h" 49 #include "packet.h" 50 51 /* 52 * this file contains utility functions that have no real better home 53 * of their own. they can largely be broken into six categories: 54 * 55 * o conversion functions -- functions to turn integers into strings, 56 * or to convert between units of a similar measure. 57 * 58 * o time and timer functions -- functions to handle time measurement 59 * and events. 60 * 61 * o ipc-related functions -- functions to simplify the generation of 62 * ipc messages to the agent's clients. 63 * 64 * o signal-related functions -- functions to clean up the agent when 65 * it receives a signal. 66 * 67 * o routing table manipulation functions 68 * 69 * o true miscellany -- anything else 70 */ 71 72 /* 73 * pkt_type_to_string(): stringifies a packet type 74 * 75 * input: uchar_t: a DHCP packet type value, RFC 2131 or 3315 76 * boolean_t: B_TRUE if IPv6 77 * output: const char *: the stringified packet type 78 */ 79 80 const char * 81 pkt_type_to_string(uchar_t type, boolean_t isv6) 82 { 83 /* 84 * note: the ordering in these arrays allows direct indexing of the 85 * table based on the RFC packet type value passed in. 86 */ 87 88 static const char *v4types[] = { 89 "BOOTP", "DISCOVER", "OFFER", "REQUEST", "DECLINE", 90 "ACK", "NAK", "RELEASE", "INFORM" 91 }; 92 static const char *v6types[] = { 93 NULL, "SOLICIT", "ADVERTISE", "REQUEST", 94 "CONFIRM", "RENEW", "REBIND", "REPLY", 95 "RELEASE", "DECLINE", "RECONFIGURE", "INFORMATION-REQUEST", 96 "RELAY-FORW", "RELAY-REPL" 97 }; 98 99 if (isv6) { 100 if (type >= sizeof (v6types) / sizeof (*v6types) || 101 v6types[type] == NULL) 102 return ("<unknown>"); 103 else 104 return (v6types[type]); 105 } else { 106 if (type >= sizeof (v4types) / sizeof (*v4types) || 107 v4types[type] == NULL) 108 return ("<unknown>"); 109 else 110 return (v4types[type]); 111 } 112 } 113 114 /* 115 * monosec_to_string(): converts a monosec_t into a date string 116 * 117 * input: monosec_t: the monosec_t to convert 118 * output: const char *: the corresponding date string 119 */ 120 121 const char * 122 monosec_to_string(monosec_t monosec) 123 { 124 time_t time = monosec_to_time(monosec); 125 char *time_string = ctime(&time); 126 127 /* strip off the newline -- ugh, why, why, why.. */ 128 time_string[strlen(time_string) - 1] = '\0'; 129 return (time_string); 130 } 131 132 /* 133 * monosec(): returns a monotonically increasing time in seconds that 134 * is not affected by stime(2) or adjtime(2). 135 * 136 * input: void 137 * output: monosec_t: the number of seconds since some time in the past 138 */ 139 140 monosec_t 141 monosec(void) 142 { 143 return (gethrtime() / NANOSEC); 144 } 145 146 /* 147 * monosec_to_time(): converts a monosec_t into real wall time 148 * 149 * input: monosec_t: the absolute monosec_t to convert 150 * output: time_t: the absolute time that monosec_t represents in wall time 151 */ 152 153 time_t 154 monosec_to_time(monosec_t abs_monosec) 155 { 156 return (abs_monosec - monosec()) + time(NULL); 157 } 158 159 /* 160 * hrtime_to_monosec(): converts a hrtime_t to monosec_t 161 * 162 * input: hrtime_t: the time to convert 163 * output: monosec_t: the time in monosec_t 164 */ 165 166 monosec_t 167 hrtime_to_monosec(hrtime_t hrtime) 168 { 169 return (hrtime / NANOSEC); 170 } 171 172 /* 173 * print_server_msg(): prints a message from a DHCP server 174 * 175 * input: dhcp_smach_t *: the state machine the message is associated with 176 * const char *: the string to display 177 * uint_t: length of string 178 * output: void 179 */ 180 181 void 182 print_server_msg(dhcp_smach_t *dsmp, const char *msg, uint_t msglen) 183 { 184 if (msglen > 0) { 185 dhcpmsg(MSG_INFO, "%s: message from server: %.*s", 186 dsmp->dsm_name, msglen, msg); 187 } 188 } 189 190 /* 191 * alrm_exit(): Signal handler for SIGARLM. terminates grandparent. 192 * 193 * input: int: signal the handler was called with. 194 * 195 * output: void 196 */ 197 198 static void 199 alrm_exit(int sig) 200 { 201 int exitval; 202 203 if (sig == SIGALRM && grandparent != 0) 204 exitval = EXIT_SUCCESS; 205 else 206 exitval = EXIT_FAILURE; 207 208 _exit(exitval); 209 } 210 211 /* 212 * daemonize(): daemonizes the process 213 * 214 * input: void 215 * output: int: 1 on success, 0 on failure 216 */ 217 218 int 219 daemonize(void) 220 { 221 /* 222 * We've found that adoption takes sufficiently long that 223 * a dhcpinfo run after dhcpagent -a is started may occur 224 * before the agent is ready to process the request. 225 * The result is an error message and an unhappy user. 226 * 227 * The initial process now sleeps for DHCP_ADOPT_SLEEP, 228 * unless interrupted by a SIGALRM, in which case it 229 * exits immediately. This has the effect that the 230 * grandparent doesn't exit until the dhcpagent is ready 231 * to process requests. This defers the the balance of 232 * the system start-up script processing until the 233 * dhcpagent is ready to field requests. 234 * 235 * grandparent is only set for the adopt case; other 236 * cases do not require the wait. 237 */ 238 239 if (grandparent != 0) 240 (void) signal(SIGALRM, alrm_exit); 241 242 switch (fork()) { 243 244 case -1: 245 return (0); 246 247 case 0: 248 if (grandparent != 0) 249 (void) signal(SIGALRM, SIG_DFL); 250 251 /* 252 * setsid() makes us lose our controlling terminal, 253 * and become both a session leader and a process 254 * group leader. 255 */ 256 257 (void) setsid(); 258 259 /* 260 * under POSIX, a session leader can accidentally 261 * (through open(2)) acquire a controlling terminal if 262 * it does not have one. just to be safe, fork again 263 * so we are not a session leader. 264 */ 265 266 switch (fork()) { 267 268 case -1: 269 return (0); 270 271 case 0: 272 (void) signal(SIGHUP, SIG_IGN); 273 (void) chdir("/"); 274 (void) umask(022); 275 closefrom(0); 276 break; 277 278 default: 279 _exit(EXIT_SUCCESS); 280 } 281 break; 282 283 default: 284 if (grandparent != 0) { 285 (void) signal(SIGCHLD, SIG_IGN); 286 /* 287 * Note that we're not the agent here, so the DHCP 288 * logging subsystem hasn't been configured yet. 289 */ 290 syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: " 291 "waiting for adoption to complete."); 292 if (sleep(DHCP_ADOPT_SLEEP) == 0) { 293 syslog(LOG_WARNING | LOG_DAEMON, 294 "dhcpagent: daemonize: timed out awaiting " 295 "adoption."); 296 } 297 syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: " 298 "wait finished"); 299 } 300 _exit(EXIT_SUCCESS); 301 } 302 303 return (1); 304 } 305 306 /* 307 * update_default_route(): update the interface's default route 308 * 309 * input: int: the type of message; either RTM_ADD or RTM_DELETE 310 * struct in_addr: the default gateway to use 311 * const char *: the interface associated with the route 312 * int: any additional flags (besides RTF_STATIC and RTF_GATEWAY) 313 * output: boolean_t: B_TRUE on success, B_FALSE on failure 314 */ 315 316 static boolean_t 317 update_default_route(uint32_t ifindex, int type, struct in_addr *gateway_nbo, 318 int flags) 319 { 320 struct { 321 struct rt_msghdr rm_mh; 322 struct sockaddr_in rm_dst; 323 struct sockaddr_in rm_gw; 324 struct sockaddr_in rm_mask; 325 struct sockaddr_dl rm_ifp; 326 } rtmsg; 327 328 (void) memset(&rtmsg, 0, sizeof (rtmsg)); 329 rtmsg.rm_mh.rtm_version = RTM_VERSION; 330 rtmsg.rm_mh.rtm_msglen = sizeof (rtmsg); 331 rtmsg.rm_mh.rtm_type = type; 332 rtmsg.rm_mh.rtm_pid = getpid(); 333 rtmsg.rm_mh.rtm_flags = RTF_GATEWAY | RTF_STATIC | flags; 334 rtmsg.rm_mh.rtm_addrs = RTA_GATEWAY | RTA_DST | RTA_NETMASK | RTA_IFP; 335 336 rtmsg.rm_gw.sin_family = AF_INET; 337 rtmsg.rm_gw.sin_addr = *gateway_nbo; 338 339 rtmsg.rm_dst.sin_family = AF_INET; 340 rtmsg.rm_dst.sin_addr.s_addr = htonl(INADDR_ANY); 341 342 rtmsg.rm_mask.sin_family = AF_INET; 343 rtmsg.rm_mask.sin_addr.s_addr = htonl(0); 344 345 rtmsg.rm_ifp.sdl_family = AF_LINK; 346 rtmsg.rm_ifp.sdl_index = ifindex; 347 348 return (write(rtsock_fd, &rtmsg, sizeof (rtmsg)) == sizeof (rtmsg)); 349 } 350 351 /* 352 * add_default_route(): add the default route to the given gateway 353 * 354 * input: const char *: the name of the interface associated with the route 355 * struct in_addr: the default gateway to add 356 * output: boolean_t: B_TRUE on success, B_FALSE otherwise 357 */ 358 359 boolean_t 360 add_default_route(uint32_t ifindex, struct in_addr *gateway_nbo) 361 { 362 return (update_default_route(ifindex, RTM_ADD, gateway_nbo, RTF_UP)); 363 } 364 365 /* 366 * del_default_route(): deletes the default route to the given gateway 367 * 368 * input: const char *: the name of the interface associated with the route 369 * struct in_addr: if not INADDR_ANY, the default gateway to remove 370 * output: boolean_t: B_TRUE on success, B_FALSE on failure 371 */ 372 373 boolean_t 374 del_default_route(uint32_t ifindex, struct in_addr *gateway_nbo) 375 { 376 if (gateway_nbo->s_addr == htonl(INADDR_ANY)) /* no router */ 377 return (B_TRUE); 378 379 return (update_default_route(ifindex, RTM_DELETE, gateway_nbo, 0)); 380 } 381 382 /* 383 * inactivity_shutdown(): shuts down agent if there are no state machines left 384 * to manage 385 * 386 * input: iu_tq_t *: unused 387 * void *: unused 388 * output: void 389 */ 390 391 /* ARGSUSED */ 392 void 393 inactivity_shutdown(iu_tq_t *tqp, void *arg) 394 { 395 if (smach_count() > 0) /* shouldn't happen, but... */ 396 return; 397 398 dhcpmsg(MSG_VERBOSE, "inactivity_shutdown: timed out"); 399 400 iu_stop_handling_events(eh, DHCP_REASON_INACTIVITY, NULL, NULL); 401 } 402 403 /* 404 * graceful_shutdown(): shuts down the agent gracefully 405 * 406 * input: int: the signal that caused graceful_shutdown to be called 407 * output: void 408 */ 409 410 void 411 graceful_shutdown(int sig) 412 { 413 iu_stop_handling_events(eh, (sig == SIGTERM ? DHCP_REASON_TERMINATE : 414 DHCP_REASON_SIGNAL), drain_script, NULL); 415 } 416 417 /* 418 * bind_sock(): binds a socket to a given IP address and port number 419 * 420 * input: int: the socket to bind 421 * in_port_t: the port number to bind to, host byte order 422 * in_addr_t: the address to bind to, host byte order 423 * output: boolean_t: B_TRUE on success, B_FALSE on failure 424 */ 425 426 boolean_t 427 bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo) 428 { 429 struct sockaddr_in sin; 430 int on = 1; 431 432 (void) memset(&sin, 0, sizeof (struct sockaddr_in)); 433 sin.sin_family = AF_INET; 434 sin.sin_port = htons(port_hbo); 435 sin.sin_addr.s_addr = htonl(addr_hbo); 436 437 (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int)); 438 439 return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0); 440 } 441 442 /* 443 * bind_sock_v6(): binds a socket to a given IP address and port number 444 * 445 * input: int: the socket to bind 446 * in_port_t: the port number to bind to, host byte order 447 * in6_addr_t: the address to bind to, network byte order 448 * output: boolean_t: B_TRUE on success, B_FALSE on failure 449 */ 450 451 boolean_t 452 bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo) 453 { 454 struct sockaddr_in6 sin6; 455 int on = 1; 456 457 (void) memset(&sin6, 0, sizeof (struct sockaddr_in6)); 458 sin6.sin6_family = AF_INET6; 459 sin6.sin6_port = htons(port_hbo); 460 if (addr_nbo != NULL) { 461 (void) memcpy(&sin6.sin6_addr, addr_nbo, 462 sizeof (sin6.sin6_addr)); 463 } 464 465 (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int)); 466 467 return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0); 468 } 469 470 /* 471 * valid_hostname(): check whether a string is a valid hostname 472 * 473 * input: const char *: the string to verify as a hostname 474 * output: boolean_t: B_TRUE if the string is a valid hostname 475 * 476 * Note that we accept both host names beginning with a digit and 477 * those containing hyphens. Neither is strictly legal according 478 * to the RFCs, but both are in common practice, so we endeavour 479 * to not break what customers are using. 480 */ 481 482 static boolean_t 483 valid_hostname(const char *hostname) 484 { 485 unsigned int i; 486 487 for (i = 0; hostname[i] != '\0'; i++) { 488 489 if (isalpha(hostname[i]) || isdigit(hostname[i]) || 490 (((hostname[i] == '-') || (hostname[i] == '.')) && (i > 0))) 491 continue; 492 493 return (B_FALSE); 494 } 495 496 return (i > 0); 497 } 498 499 /* 500 * iffile_to_hostname(): return the hostname contained on a line of the form 501 * 502 * [ ^I]*inet[ ^I]+hostname[\n]*\0 503 * 504 * in the file located at the specified path 505 * 506 * input: const char *: the path of the file to look in for the hostname 507 * output: const char *: the hostname at that path, or NULL on failure 508 */ 509 510 #define IFLINE_MAX 1024 /* maximum length of a hostname.<if> line */ 511 512 const char * 513 iffile_to_hostname(const char *path) 514 { 515 FILE *fp; 516 static char ifline[IFLINE_MAX]; 517 518 fp = fopen(path, "r"); 519 if (fp == NULL) 520 return (NULL); 521 522 /* 523 * /etc/hostname.<if> may contain multiple ifconfig commands, but each 524 * such command is on a separate line (see the "while read ifcmds" code 525 * in /etc/init.d/inetinit). Thus we will read the file a line at a 526 * time, searching for a line of the form 527 * 528 * [ ^I]*inet[ ^I]+hostname[\n]*\0 529 * 530 * extract the host name from it, and check it for validity. 531 */ 532 while (fgets(ifline, sizeof (ifline), fp) != NULL) { 533 char *p; 534 535 if ((p = strstr(ifline, "inet")) != NULL) { 536 if ((p != ifline) && !isspace(p[-1])) { 537 (void) fclose(fp); 538 return (NULL); 539 } 540 p += 4; /* skip over "inet" and expect spaces or tabs */ 541 if ((*p == '\n') || (*p == '\0')) { 542 (void) fclose(fp); 543 return (NULL); 544 } 545 if (isspace(*p)) { 546 char *nlptr; 547 548 /* no need to read more of the file */ 549 (void) fclose(fp); 550 551 while (isspace(*p)) 552 p++; 553 if ((nlptr = strrchr(p, '\n')) != NULL) 554 *nlptr = '\0'; 555 if (strlen(p) > MAXHOSTNAMELEN) { 556 dhcpmsg(MSG_WARNING, 557 "iffile_to_hostname:" 558 " host name too long"); 559 return (NULL); 560 } 561 if (valid_hostname(p)) { 562 return (p); 563 } else { 564 dhcpmsg(MSG_WARNING, 565 "iffile_to_hostname:" 566 " host name not valid"); 567 return (NULL); 568 } 569 } else { 570 (void) fclose(fp); 571 return (NULL); 572 } 573 } 574 } 575 576 (void) fclose(fp); 577 return (NULL); 578 } 579 580 /* 581 * init_timer(): set up a DHCP timer 582 * 583 * input: dhcp_timer_t *: the timer to set up 584 * output: void 585 */ 586 587 void 588 init_timer(dhcp_timer_t *dt, lease_t startval) 589 { 590 dt->dt_id = -1; 591 dt->dt_start = startval; 592 } 593 594 /* 595 * cancel_timer(): cancel a DHCP timer 596 * 597 * input: dhcp_timer_t *: the timer to cancel 598 * output: boolean_t: B_TRUE on success, B_FALSE otherwise 599 */ 600 601 boolean_t 602 cancel_timer(dhcp_timer_t *dt) 603 { 604 if (dt->dt_id == -1) 605 return (B_TRUE); 606 607 if (iu_cancel_timer(tq, dt->dt_id, NULL) == 1) { 608 dt->dt_id = -1; 609 return (B_TRUE); 610 } 611 612 return (B_FALSE); 613 } 614 615 /* 616 * schedule_timer(): schedule a DHCP timer. Note that it must not be already 617 * running, and that we can't cancel here. If it were, and 618 * we did, we'd leak a reference to the callback argument. 619 * 620 * input: dhcp_timer_t *: the timer to schedule 621 * output: boolean_t: B_TRUE on success, B_FALSE otherwise 622 */ 623 624 boolean_t 625 schedule_timer(dhcp_timer_t *dt, iu_tq_callback_t *cbfunc, void *arg) 626 { 627 if (dt->dt_id != -1) 628 return (B_FALSE); 629 dt->dt_id = iu_schedule_timer(tq, dt->dt_start, cbfunc, arg); 630 return (dt->dt_id != -1); 631 } 632 633 /* 634 * dhcpv6_status_code(): report on a DHCPv6 status code found in an option 635 * buffer. 636 * 637 * input: const dhcpv6_option_t *: pointer to option 638 * uint_t: option length 639 * const char **: error string (nul-terminated) 640 * const char **: message from server (unterminated) 641 * uint_t *: length of server message 642 * output: int: -1 on error, or >= 0 for a DHCPv6 status code 643 */ 644 645 int 646 dhcpv6_status_code(const dhcpv6_option_t *d6o, uint_t olen, const char **estr, 647 const char **msg, uint_t *msglenp) 648 { 649 uint16_t status; 650 static const char *v6_status[] = { 651 NULL, 652 "Unknown reason", 653 "Server has no addresses available", 654 "Client record unavailable", 655 "Prefix inappropriate for link", 656 "Client must use multicast", 657 "No prefix available" 658 }; 659 static char sbuf[32]; 660 661 *estr = ""; 662 *msg = ""; 663 *msglenp = 0; 664 if (d6o == NULL) 665 return (0); 666 olen -= sizeof (*d6o); 667 if (olen < 2) { 668 *estr = "garbled status code"; 669 return (-1); 670 } 671 672 *msg = (const char *)(d6o + 1) + 2; 673 *msglenp = olen - 2; 674 675 (void) memcpy(&status, d6o + 1, sizeof (status)); 676 status = ntohs(status); 677 if (status > 0) { 678 if (status > DHCPV6_STAT_NOPREFIX) { 679 (void) snprintf(sbuf, sizeof (sbuf), "status %u", 680 status); 681 *estr = sbuf; 682 } else { 683 *estr = v6_status[status]; 684 } 685 } 686 return (status); 687 } 688