Mercurial > illumos > onarm
diff usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c @ 0:c9caec207d52 b86
Initial porting based on b86
author | Koji Uno <koji.uno@sun.com> |
---|---|
date | Tue, 02 Jun 2009 18:56:50 +0900 |
parents | |
children | 1a15d5aaf794 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c Tue Jun 02 18:56:50 2009 +0900 @@ -0,0 +1,687 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "@(#)util.c 1.25 07/04/04 SMI" + +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <netinet/in.h> /* struct in_addr */ +#include <netinet/dhcp.h> +#include <signal.h> +#include <sys/socket.h> +#include <net/route.h> +#include <net/if_arp.h> +#include <string.h> +#include <dhcpmsg.h> +#include <ctype.h> +#include <netdb.h> +#include <fcntl.h> +#include <stdio.h> + +#include "states.h" +#include "agent.h" +#include "interface.h" +#include "util.h" +#include "packet.h" + +/* + * this file contains utility functions that have no real better home + * of their own. they can largely be broken into six categories: + * + * o conversion functions -- functions to turn integers into strings, + * or to convert between units of a similar measure. + * + * o time and timer functions -- functions to handle time measurement + * and events. + * + * o ipc-related functions -- functions to simplify the generation of + * ipc messages to the agent's clients. + * + * o signal-related functions -- functions to clean up the agent when + * it receives a signal. + * + * o routing table manipulation functions + * + * o true miscellany -- anything else + */ + +/* + * pkt_type_to_string(): stringifies a packet type + * + * input: uchar_t: a DHCP packet type value, RFC 2131 or 3315 + * boolean_t: B_TRUE if IPv6 + * output: const char *: the stringified packet type + */ + +const char * +pkt_type_to_string(uchar_t type, boolean_t isv6) +{ + /* + * note: the ordering in these arrays allows direct indexing of the + * table based on the RFC packet type value passed in. + */ + + static const char *v4types[] = { + "BOOTP", "DISCOVER", "OFFER", "REQUEST", "DECLINE", + "ACK", "NAK", "RELEASE", "INFORM" + }; + static const char *v6types[] = { + NULL, "SOLICIT", "ADVERTISE", "REQUEST", + "CONFIRM", "RENEW", "REBIND", "REPLY", + "RELEASE", "DECLINE", "RECONFIGURE", "INFORMATION-REQUEST", + "RELAY-FORW", "RELAY-REPL" + }; + + if (isv6) { + if (type >= sizeof (v6types) / sizeof (*v6types) || + v6types[type] == NULL) + return ("<unknown>"); + else + return (v6types[type]); + } else { + if (type >= sizeof (v4types) / sizeof (*v4types) || + v4types[type] == NULL) + return ("<unknown>"); + else + return (v4types[type]); + } +} + +/* + * monosec_to_string(): converts a monosec_t into a date string + * + * input: monosec_t: the monosec_t to convert + * output: const char *: the corresponding date string + */ + +const char * +monosec_to_string(monosec_t monosec) +{ + time_t time = monosec_to_time(monosec); + char *time_string = ctime(&time); + + /* strip off the newline -- ugh, why, why, why.. */ + time_string[strlen(time_string) - 1] = '\0'; + return (time_string); +} + +/* + * monosec(): returns a monotonically increasing time in seconds that + * is not affected by stime(2) or adjtime(2). + * + * input: void + * output: monosec_t: the number of seconds since some time in the past + */ + +monosec_t +monosec(void) +{ + return (gethrtime() / NANOSEC); +} + +/* + * monosec_to_time(): converts a monosec_t into real wall time + * + * input: monosec_t: the absolute monosec_t to convert + * output: time_t: the absolute time that monosec_t represents in wall time + */ + +time_t +monosec_to_time(monosec_t abs_monosec) +{ + return (abs_monosec - monosec()) + time(NULL); +} + +/* + * hrtime_to_monosec(): converts a hrtime_t to monosec_t + * + * input: hrtime_t: the time to convert + * output: monosec_t: the time in monosec_t + */ + +monosec_t +hrtime_to_monosec(hrtime_t hrtime) +{ + return (hrtime / NANOSEC); +} + +/* + * print_server_msg(): prints a message from a DHCP server + * + * input: dhcp_smach_t *: the state machine the message is associated with + * const char *: the string to display + * uint_t: length of string + * output: void + */ + +void +print_server_msg(dhcp_smach_t *dsmp, const char *msg, uint_t msglen) +{ + if (msglen > 0) { + dhcpmsg(MSG_INFO, "%s: message from server: %.*s", + dsmp->dsm_name, msglen, msg); + } +} + +/* + * alrm_exit(): Signal handler for SIGARLM. terminates grandparent. + * + * input: int: signal the handler was called with. + * + * output: void + */ + +static void +alrm_exit(int sig) +{ + int exitval; + + if (sig == SIGALRM && grandparent != 0) + exitval = EXIT_SUCCESS; + else + exitval = EXIT_FAILURE; + + _exit(exitval); +} + +/* + * daemonize(): daemonizes the process + * + * input: void + * output: int: 1 on success, 0 on failure + */ + +int +daemonize(void) +{ + /* + * We've found that adoption takes sufficiently long that + * a dhcpinfo run after dhcpagent -a is started may occur + * before the agent is ready to process the request. + * The result is an error message and an unhappy user. + * + * The initial process now sleeps for DHCP_ADOPT_SLEEP, + * unless interrupted by a SIGALRM, in which case it + * exits immediately. This has the effect that the + * grandparent doesn't exit until the dhcpagent is ready + * to process requests. This defers the the balance of + * the system start-up script processing until the + * dhcpagent is ready to field requests. + * + * grandparent is only set for the adopt case; other + * cases do not require the wait. + */ + + if (grandparent != 0) + (void) signal(SIGALRM, alrm_exit); + + switch (fork()) { + + case -1: + return (0); + + case 0: + if (grandparent != 0) + (void) signal(SIGALRM, SIG_DFL); + + /* + * setsid() makes us lose our controlling terminal, + * and become both a session leader and a process + * group leader. + */ + + (void) setsid(); + + /* + * under POSIX, a session leader can accidentally + * (through open(2)) acquire a controlling terminal if + * it does not have one. just to be safe, fork again + * so we are not a session leader. + */ + + switch (fork()) { + + case -1: + return (0); + + case 0: + (void) signal(SIGHUP, SIG_IGN); + (void) chdir("/"); + (void) umask(022); + closefrom(0); + break; + + default: + _exit(EXIT_SUCCESS); + } + break; + + default: + if (grandparent != 0) { + (void) signal(SIGCHLD, SIG_IGN); + /* + * Note that we're not the agent here, so the DHCP + * logging subsystem hasn't been configured yet. + */ + syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: " + "waiting for adoption to complete."); + if (sleep(DHCP_ADOPT_SLEEP) == 0) { + syslog(LOG_WARNING | LOG_DAEMON, + "dhcpagent: daemonize: timed out awaiting " + "adoption."); + } + syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: " + "wait finished"); + } + _exit(EXIT_SUCCESS); + } + + return (1); +} + +/* + * update_default_route(): update the interface's default route + * + * input: int: the type of message; either RTM_ADD or RTM_DELETE + * struct in_addr: the default gateway to use + * const char *: the interface associated with the route + * int: any additional flags (besides RTF_STATIC and RTF_GATEWAY) + * output: boolean_t: B_TRUE on success, B_FALSE on failure + */ + +static boolean_t +update_default_route(uint32_t ifindex, int type, struct in_addr *gateway_nbo, + int flags) +{ + struct { + struct rt_msghdr rm_mh; + struct sockaddr_in rm_dst; + struct sockaddr_in rm_gw; + struct sockaddr_in rm_mask; + struct sockaddr_dl rm_ifp; + } rtmsg; + + (void) memset(&rtmsg, 0, sizeof (rtmsg)); + rtmsg.rm_mh.rtm_version = RTM_VERSION; + rtmsg.rm_mh.rtm_msglen = sizeof (rtmsg); + rtmsg.rm_mh.rtm_type = type; + rtmsg.rm_mh.rtm_pid = getpid(); + rtmsg.rm_mh.rtm_flags = RTF_GATEWAY | RTF_STATIC | flags; + rtmsg.rm_mh.rtm_addrs = RTA_GATEWAY | RTA_DST | RTA_NETMASK | RTA_IFP; + + rtmsg.rm_gw.sin_family = AF_INET; + rtmsg.rm_gw.sin_addr = *gateway_nbo; + + rtmsg.rm_dst.sin_family = AF_INET; + rtmsg.rm_dst.sin_addr.s_addr = htonl(INADDR_ANY); + + rtmsg.rm_mask.sin_family = AF_INET; + rtmsg.rm_mask.sin_addr.s_addr = htonl(0); + + rtmsg.rm_ifp.sdl_family = AF_LINK; + rtmsg.rm_ifp.sdl_index = ifindex; + + return (write(rtsock_fd, &rtmsg, sizeof (rtmsg)) == sizeof (rtmsg)); +} + +/* + * add_default_route(): add the default route to the given gateway + * + * input: const char *: the name of the interface associated with the route + * struct in_addr: the default gateway to add + * output: boolean_t: B_TRUE on success, B_FALSE otherwise + */ + +boolean_t +add_default_route(uint32_t ifindex, struct in_addr *gateway_nbo) +{ + return (update_default_route(ifindex, RTM_ADD, gateway_nbo, RTF_UP)); +} + +/* + * del_default_route(): deletes the default route to the given gateway + * + * input: const char *: the name of the interface associated with the route + * struct in_addr: if not INADDR_ANY, the default gateway to remove + * output: boolean_t: B_TRUE on success, B_FALSE on failure + */ + +boolean_t +del_default_route(uint32_t ifindex, struct in_addr *gateway_nbo) +{ + if (gateway_nbo->s_addr == htonl(INADDR_ANY)) /* no router */ + return (B_TRUE); + + return (update_default_route(ifindex, RTM_DELETE, gateway_nbo, 0)); +} + +/* + * inactivity_shutdown(): shuts down agent if there are no state machines left + * to manage + * + * input: iu_tq_t *: unused + * void *: unused + * output: void + */ + +/* ARGSUSED */ +void +inactivity_shutdown(iu_tq_t *tqp, void *arg) +{ + if (smach_count() > 0) /* shouldn't happen, but... */ + return; + + dhcpmsg(MSG_VERBOSE, "inactivity_shutdown: timed out"); + + iu_stop_handling_events(eh, DHCP_REASON_INACTIVITY, NULL, NULL); +} + +/* + * graceful_shutdown(): shuts down the agent gracefully + * + * input: int: the signal that caused graceful_shutdown to be called + * output: void + */ + +void +graceful_shutdown(int sig) +{ + iu_stop_handling_events(eh, (sig == SIGTERM ? DHCP_REASON_TERMINATE : + DHCP_REASON_SIGNAL), drain_script, NULL); +} + +/* + * bind_sock(): binds a socket to a given IP address and port number + * + * input: int: the socket to bind + * in_port_t: the port number to bind to, host byte order + * in_addr_t: the address to bind to, host byte order + * output: boolean_t: B_TRUE on success, B_FALSE on failure + */ + +boolean_t +bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo) +{ + struct sockaddr_in sin; + int on = 1; + + (void) memset(&sin, 0, sizeof (struct sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_port = htons(port_hbo); + sin.sin_addr.s_addr = htonl(addr_hbo); + + (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int)); + + return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0); +} + +/* + * bind_sock_v6(): binds a socket to a given IP address and port number + * + * input: int: the socket to bind + * in_port_t: the port number to bind to, host byte order + * in6_addr_t: the address to bind to, network byte order + * output: boolean_t: B_TRUE on success, B_FALSE on failure + */ + +boolean_t +bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo) +{ + struct sockaddr_in6 sin6; + int on = 1; + + (void) memset(&sin6, 0, sizeof (struct sockaddr_in6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(port_hbo); + if (addr_nbo != NULL) { + (void) memcpy(&sin6.sin6_addr, addr_nbo, + sizeof (sin6.sin6_addr)); + } + + (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int)); + + return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0); +} + +/* + * valid_hostname(): check whether a string is a valid hostname + * + * input: const char *: the string to verify as a hostname + * output: boolean_t: B_TRUE if the string is a valid hostname + * + * Note that we accept both host names beginning with a digit and + * those containing hyphens. Neither is strictly legal according + * to the RFCs, but both are in common practice, so we endeavour + * to not break what customers are using. + */ + +static boolean_t +valid_hostname(const char *hostname) +{ + unsigned int i; + + for (i = 0; hostname[i] != '\0'; i++) { + + if (isalpha(hostname[i]) || isdigit(hostname[i]) || + (((hostname[i] == '-') || (hostname[i] == '.')) && (i > 0))) + continue; + + return (B_FALSE); + } + + return (i > 0); +} + +/* + * iffile_to_hostname(): return the hostname contained on a line of the form + * + * [ ^I]*inet[ ^I]+hostname[\n]*\0 + * + * in the file located at the specified path + * + * input: const char *: the path of the file to look in for the hostname + * output: const char *: the hostname at that path, or NULL on failure + */ + +#define IFLINE_MAX 1024 /* maximum length of a hostname.<if> line */ + +const char * +iffile_to_hostname(const char *path) +{ + FILE *fp; + static char ifline[IFLINE_MAX]; + + fp = fopen(path, "r"); + if (fp == NULL) + return (NULL); + + /* + * /etc/hostname.<if> may contain multiple ifconfig commands, but each + * such command is on a separate line (see the "while read ifcmds" code + * in /etc/init.d/inetinit). Thus we will read the file a line at a + * time, searching for a line of the form + * + * [ ^I]*inet[ ^I]+hostname[\n]*\0 + * + * extract the host name from it, and check it for validity. + */ + while (fgets(ifline, sizeof (ifline), fp) != NULL) { + char *p; + + if ((p = strstr(ifline, "inet")) != NULL) { + if ((p != ifline) && !isspace(p[-1])) { + (void) fclose(fp); + return (NULL); + } + p += 4; /* skip over "inet" and expect spaces or tabs */ + if ((*p == '\n') || (*p == '\0')) { + (void) fclose(fp); + return (NULL); + } + if (isspace(*p)) { + char *nlptr; + + /* no need to read more of the file */ + (void) fclose(fp); + + while (isspace(*p)) + p++; + if ((nlptr = strrchr(p, '\n')) != NULL) + *nlptr = '\0'; + if (strlen(p) > MAXHOSTNAMELEN) { + dhcpmsg(MSG_WARNING, + "iffile_to_hostname:" + " host name too long"); + return (NULL); + } + if (valid_hostname(p)) { + return (p); + } else { + dhcpmsg(MSG_WARNING, + "iffile_to_hostname:" + " host name not valid"); + return (NULL); + } + } else { + (void) fclose(fp); + return (NULL); + } + } + } + + (void) fclose(fp); + return (NULL); +} + +/* + * init_timer(): set up a DHCP timer + * + * input: dhcp_timer_t *: the timer to set up + * output: void + */ + +void +init_timer(dhcp_timer_t *dt, lease_t startval) +{ + dt->dt_id = -1; + dt->dt_start = startval; +} + +/* + * cancel_timer(): cancel a DHCP timer + * + * input: dhcp_timer_t *: the timer to cancel + * output: boolean_t: B_TRUE on success, B_FALSE otherwise + */ + +boolean_t +cancel_timer(dhcp_timer_t *dt) +{ + if (dt->dt_id == -1) + return (B_TRUE); + + if (iu_cancel_timer(tq, dt->dt_id, NULL) == 1) { + dt->dt_id = -1; + return (B_TRUE); + } + + return (B_FALSE); +} + +/* + * schedule_timer(): schedule a DHCP timer. Note that it must not be already + * running, and that we can't cancel here. If it were, and + * we did, we'd leak a reference to the callback argument. + * + * input: dhcp_timer_t *: the timer to schedule + * output: boolean_t: B_TRUE on success, B_FALSE otherwise + */ + +boolean_t +schedule_timer(dhcp_timer_t *dt, iu_tq_callback_t *cbfunc, void *arg) +{ + if (dt->dt_id != -1) + return (B_FALSE); + dt->dt_id = iu_schedule_timer(tq, dt->dt_start, cbfunc, arg); + return (dt->dt_id != -1); +} + +/* + * dhcpv6_status_code(): report on a DHCPv6 status code found in an option + * buffer. + * + * input: const dhcpv6_option_t *: pointer to option + * uint_t: option length + * const char **: error string (nul-terminated) + * const char **: message from server (unterminated) + * uint_t *: length of server message + * output: int: -1 on error, or >= 0 for a DHCPv6 status code + */ + +int +dhcpv6_status_code(const dhcpv6_option_t *d6o, uint_t olen, const char **estr, + const char **msg, uint_t *msglenp) +{ + uint16_t status; + static const char *v6_status[] = { + NULL, + "Unknown reason", + "Server has no addresses available", + "Client record unavailable", + "Prefix inappropriate for link", + "Client must use multicast", + "No prefix available" + }; + static char sbuf[32]; + + *estr = ""; + *msg = ""; + *msglenp = 0; + if (d6o == NULL) + return (0); + olen -= sizeof (*d6o); + if (olen < 2) { + *estr = "garbled status code"; + return (-1); + } + + *msg = (const char *)(d6o + 1) + 2; + *msglenp = olen - 2; + + (void) memcpy(&status, d6o + 1, sizeof (status)); + status = ntohs(status); + if (status > 0) { + if (status > DHCPV6_STAT_NOPREFIX) { + (void) snprintf(sbuf, sizeof (sbuf), "status %u", + status); + *estr = sbuf; + } else { + *estr = v6_status[status]; + } + } + return (status); +}