From 61fb48930f45aa536584b2047f9e703186e8f69f Mon Sep 17 00:00:00 2001 From: Russ Dill Date: Mon, 14 Oct 2002 21:41:28 +0000 Subject: added full udhcp integration --- networking/udhcp/dhcpc.c | 559 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 559 insertions(+) create mode 100644 networking/udhcp/dhcpc.c (limited to 'networking/udhcp/dhcpc.c') diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c new file mode 100644 index 000000000..ae40ec9c2 --- /dev/null +++ b/networking/udhcp/dhcpc.c @@ -0,0 +1,559 @@ +/* dhcpc.c + * + * udhcp DHCP client + * + * Russ Dill July 2001 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dhcpd.h" +#include "dhcpc.h" +#include "options.h" +#include "clientpacket.h" +#include "packet.h" +#include "script.h" +#include "socket.h" +#include "debug.h" +#include "pidfile.h" + +static int state; +static unsigned long requested_ip; /* = 0 */ +static unsigned long server_addr; +static unsigned long timeout; +static int packet_num; /* = 0 */ +static int fd; +static int signal_pipe[2]; + +#define LISTEN_NONE 0 +#define LISTEN_KERNEL 1 +#define LISTEN_RAW 2 +static int listen_mode; + +#define DEFAULT_SCRIPT "/usr/share/udhcpc/default.script" + +struct client_config_t client_config = { + /* Default options. */ + abort_if_no_lease: 0, + foreground: 0, + quit_after_lease: 0, + background_if_no_lease: 0, + interface: "eth0", + pidfile: NULL, + script: DEFAULT_SCRIPT, + clientid: NULL, + hostname: NULL, + ifindex: 0, + arp: "\0\0\0\0\0\0", /* appease gcc-3.0 */ +}; + +#ifndef BB_VER +static void show_usage(void) +{ + printf( +"Usage: udhcpc [OPTIONS]\n\n" +" -c, --clientid=CLIENTID Client identifier\n" +" -H, --hostname=HOSTNAME Client hostname\n" +" -h Alias for -H\n" +" -f, --foreground Do not fork after getting lease\n" +" -b, --background Fork to background if lease cannot be\n" +" immediately negotiated.\n" +" -i, --interface=INTERFACE Interface to use (default: eth0)\n" +" -n, --now Exit with failure if lease cannot be\n" +" immediately negotiated.\n" +" -p, --pidfile=file Store process ID of daemon in file\n" +" -q, --quit Quit after obtaining lease\n" +" -r, --request=IP IP address to request (default: none)\n" +" -s, --script=file Run file at dhcp events (default:\n" +" " DEFAULT_SCRIPT ")\n" +" -v, --version Display version\n" + ); + exit(0); +} +#endif + + +/* just a little helper */ +static void change_mode(int new_mode) +{ + DEBUG(LOG_INFO, "entering %s listen mode", + new_mode ? (new_mode == 1 ? "kernel" : "raw") : "none"); + close(fd); + fd = -1; + listen_mode = new_mode; +} + + +/* perform a renew */ +static void perform_renew(void) +{ + LOG(LOG_INFO, "Performing a DHCP renew"); + switch (state) { + case RENEWING: + run_script(NULL, "deconfig"); + case BOUND: + case REBINDING: + change_mode(LISTEN_KERNEL); + state = RENEW_REQUESTED; + break; + case RENEW_REQUESTED: + case REQUESTING: + case RELEASED: + change_mode(LISTEN_RAW); + state = INIT_SELECTING; + break; + case INIT_SELECTING: + } + + /* start things over */ + packet_num = 0; + + /* Kill any timeouts because the user wants this to hurry along */ + timeout = 0; +} + + +/* perform a release */ +static void perform_release(void) +{ + char buffer[16]; + struct in_addr temp_addr; + + /* send release packet */ + if (state == BOUND || state == RENEWING || state == REBINDING) { + temp_addr.s_addr = server_addr; + sprintf(buffer, "%s", inet_ntoa(temp_addr)); + temp_addr.s_addr = requested_ip; + LOG(LOG_INFO, "Unicasting a release of %s to %s", + inet_ntoa(temp_addr), buffer); + send_release(server_addr, requested_ip); /* unicast */ + run_script(NULL, "deconfig"); + } + LOG(LOG_INFO, "Entering released state"); + + change_mode(LISTEN_NONE); + state = RELEASED; + timeout = 0x7fffffff; +} + + +/* Exit and cleanup */ +static void exit_client(int retval) +{ + pidfile_delete(client_config.pidfile); + CLOSE_LOG(); + exit(retval); +} + + +/* Signal handler */ +static void signal_handler(int sig) +{ + if (send(signal_pipe[1], &sig, sizeof(sig), MSG_DONTWAIT) < 0) { + LOG(LOG_ERR, "Could not send signal: %s", + strerror(errno)); + } +} + + +static void background(void) +{ + int pid_fd; + + pid_fd = pidfile_acquire(client_config.pidfile); /* hold lock during fork. */ + while (pid_fd >= 0 && pid_fd < 3) pid_fd = dup(pid_fd); /* don't let daemon close it */ + if (daemon(0, 0) == -1) { + perror("fork"); + exit_client(1); + } + client_config.foreground = 1; /* Do not fork again. */ + pidfile_write_release(pid_fd); +} + + +#ifdef COMBINED_BINARY +int udhcpc_main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + unsigned char *temp, *message; + unsigned long t1 = 0, t2 = 0, xid = 0; + unsigned long start = 0, lease; + fd_set rfds; + int retval; + struct timeval tv; + int c, len; + struct dhcpMessage packet; + struct in_addr temp_addr; + int pid_fd; + time_t now; + int max_fd; + int sig; + + static struct option arg_options[] = { + {"clientid", required_argument, 0, 'c'}, + {"foreground", no_argument, 0, 'f'}, + {"background", no_argument, 0, 'b'}, + {"hostname", required_argument, 0, 'H'}, + {"hostname", required_argument, 0, 'h'}, + {"interface", required_argument, 0, 'i'}, + {"now", no_argument, 0, 'n'}, + {"pidfile", required_argument, 0, 'p'}, + {"quit", no_argument, 0, 'q'}, + {"request", required_argument, 0, 'r'}, + {"script", required_argument, 0, 's'}, + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, '?'}, + {0, 0, 0, 0} + }; + + /* get options */ + while (1) { + int option_index = 0; + c = getopt_long(argc, argv, "c:fbH:h:i:np:qr:s:v", arg_options, &option_index); + if (c == -1) break; + + switch (c) { + case 'c': + len = strlen(optarg) > 255 ? 255 : strlen(optarg); + if (client_config.clientid) free(client_config.clientid); + client_config.clientid = xmalloc(len + 2); + client_config.clientid[OPT_CODE] = DHCP_CLIENT_ID; + client_config.clientid[OPT_LEN] = len; + client_config.clientid[OPT_DATA] = '\0'; + strncpy(client_config.clientid + OPT_DATA, optarg, len); + break; + case 'f': + client_config.foreground = 1; + break; + case 'b': + client_config.background_if_no_lease = 1; + break; + case 'h': + case 'H': + len = strlen(optarg) > 255 ? 255 : strlen(optarg); + if (client_config.hostname) free(client_config.hostname); + client_config.hostname = xmalloc(len + 2); + client_config.hostname[OPT_CODE] = DHCP_HOST_NAME; + client_config.hostname[OPT_LEN] = len; + strncpy(client_config.hostname + 2, optarg, len); + break; + case 'i': + client_config.interface = optarg; + break; + case 'n': + client_config.abort_if_no_lease = 1; + break; + case 'p': + client_config.pidfile = optarg; + break; + case 'q': + client_config.quit_after_lease = 1; + break; + case 'r': + requested_ip = inet_addr(optarg); + break; + case 's': + client_config.script = optarg; + break; + case 'v': + printf("udhcpcd, version %s\n\n", VERSION); + exit_client(0); + break; + default: + show_usage(); + } + } + + OPEN_LOG("udhcpc"); + LOG(LOG_INFO, "udhcp client (v%s) started", VERSION); + + pid_fd = pidfile_acquire(client_config.pidfile); + pidfile_write_release(pid_fd); + + if (read_interface(client_config.interface, &client_config.ifindex, + NULL, client_config.arp) < 0) + exit_client(1); + + if (!client_config.clientid) { + client_config.clientid = xmalloc(6 + 3); + client_config.clientid[OPT_CODE] = DHCP_CLIENT_ID; + client_config.clientid[OPT_LEN] = 7; + client_config.clientid[OPT_DATA] = 1; + memcpy(client_config.clientid + 3, client_config.arp, 6); + } + + /* setup signal handlers */ + socketpair(AF_UNIX, SOCK_STREAM, 0, signal_pipe); + signal(SIGUSR1, signal_handler); + signal(SIGUSR2, signal_handler); + signal(SIGTERM, signal_handler); + + state = INIT_SELECTING; + run_script(NULL, "deconfig"); + change_mode(LISTEN_RAW); + + for (;;) { + + tv.tv_sec = timeout - time(0); + tv.tv_usec = 0; + FD_ZERO(&rfds); + + if (listen_mode != LISTEN_NONE && fd < 0) { + if (listen_mode == LISTEN_KERNEL) + fd = listen_socket(INADDR_ANY, CLIENT_PORT, client_config.interface); + else + fd = raw_socket(client_config.ifindex); + if (fd < 0) { + LOG(LOG_ERR, "FATAL: couldn't listen on socket, %s", strerror(errno)); + exit_client(0); + } + } + if (fd >= 0) FD_SET(fd, &rfds); + FD_SET(signal_pipe[0], &rfds); + + if (tv.tv_sec > 0) { + DEBUG(LOG_INFO, "Waiting on select...\n"); + max_fd = signal_pipe[0] > fd ? signal_pipe[0] : fd; + retval = select(max_fd + 1, &rfds, NULL, NULL, &tv); + } else retval = 0; /* If we already timed out, fall through */ + + now = time(0); + if (retval == 0) { + /* timeout dropped to zero */ + switch (state) { + case INIT_SELECTING: + if (packet_num < 3) { + if (packet_num == 0) + xid = random_xid(); + + /* send discover packet */ + send_discover(xid, requested_ip); /* broadcast */ + + timeout = now + ((packet_num == 2) ? 4 : 2); + packet_num++; + } else { + if (client_config.background_if_no_lease) { + LOG(LOG_INFO, "No lease, forking to background."); + background(); + } else if (client_config.abort_if_no_lease) { + LOG(LOG_INFO, "No lease, failing."); + exit_client(1); + } + /* wait to try again */ + packet_num = 0; + timeout = now + 60; + } + break; + case RENEW_REQUESTED: + case REQUESTING: + if (packet_num < 3) { + /* send request packet */ + if (state == RENEW_REQUESTED) + send_renew(xid, server_addr, requested_ip); /* unicast */ + else send_selecting(xid, server_addr, requested_ip); /* broadcast */ + + timeout = now + ((packet_num == 2) ? 10 : 2); + packet_num++; + } else { + /* timed out, go back to init state */ + state = INIT_SELECTING; + timeout = now; + packet_num = 0; + change_mode(LISTEN_RAW); + } + break; + case BOUND: + /* Lease is starting to run out, time to enter renewing state */ + state = RENEWING; + change_mode(LISTEN_KERNEL); + DEBUG(LOG_INFO, "Entering renew state"); + /* fall right through */ + case RENEWING: + /* Either set a new T1, or enter REBINDING state */ + if ((t2 - t1) <= (lease / 14400 + 1)) { + /* timed out, enter rebinding state */ + state = REBINDING; + timeout = now + (t2 - t1); + DEBUG(LOG_INFO, "Entering rebinding state"); + } else { + /* send a request packet */ + send_renew(xid, server_addr, requested_ip); /* unicast */ + + t1 = (t2 - t1) / 2 + t1; + timeout = t1 + start; + } + break; + case REBINDING: + /* Either set a new T2, or enter INIT state */ + if ((lease - t2) <= (lease / 14400 + 1)) { + /* timed out, enter init state */ + state = INIT_SELECTING; + LOG(LOG_INFO, "Lease lost, entering init state"); + run_script(NULL, "deconfig"); + timeout = now; + packet_num = 0; + change_mode(LISTEN_RAW); + } else { + /* send a request packet */ + send_renew(xid, 0, requested_ip); /* broadcast */ + + t2 = (lease - t2) / 2 + t2; + timeout = t2 + start; + } + break; + case RELEASED: + /* yah, I know, *you* say it would never happen */ + timeout = 0x7fffffff; + break; + } + } else if (retval > 0 && listen_mode != LISTEN_NONE && FD_ISSET(fd, &rfds)) { + /* a packet is ready, read it */ + + if (listen_mode == LISTEN_KERNEL) + len = get_packet(&packet, fd); + else len = get_raw_packet(&packet, fd); + + if (len == -1 && errno != EINTR) { + DEBUG(LOG_INFO, "error on read, %s, reopening socket", strerror(errno)); + change_mode(listen_mode); /* just close and reopen */ + } + if (len < 0) continue; + + if (packet.xid != xid) { + DEBUG(LOG_INFO, "Ignoring XID %lx (our xid is %lx)", + (unsigned long) packet.xid, xid); + continue; + } + + if ((message = get_option(&packet, DHCP_MESSAGE_TYPE)) == NULL) { + DEBUG(LOG_ERR, "couldnt get option from packet -- ignoring"); + continue; + } + + switch (state) { + case INIT_SELECTING: + /* Must be a DHCPOFFER to one of our xid's */ + if (*message == DHCPOFFER) { + if ((temp = get_option(&packet, DHCP_SERVER_ID))) { + memcpy(&server_addr, temp, 4); + xid = packet.xid; + requested_ip = packet.yiaddr; + + /* enter requesting state */ + state = REQUESTING; + timeout = now; + packet_num = 0; + } else { + DEBUG(LOG_ERR, "No server ID in message"); + } + } + break; + case RENEW_REQUESTED: + case REQUESTING: + case RENEWING: + case REBINDING: + if (*message == DHCPACK) { + if (!(temp = get_option(&packet, DHCP_LEASE_TIME))) { + LOG(LOG_ERR, "No lease time with ACK, using 1 hour lease"); + lease = 60 * 60; + } else { + memcpy(&lease, temp, 4); + lease = ntohl(lease); + } + + /* enter bound state */ + t1 = lease / 2; + + /* little fixed point for n * .875 */ + t2 = (lease * 0x7) >> 3; + temp_addr.s_addr = packet.yiaddr; + LOG(LOG_INFO, "Lease of %s obtained, lease time %ld", + inet_ntoa(temp_addr), lease); + start = now; + timeout = t1 + start; + requested_ip = packet.yiaddr; + run_script(&packet, + ((state == RENEWING || state == REBINDING) ? "renew" : "bound")); + + state = BOUND; + change_mode(LISTEN_NONE); + if (client_config.quit_after_lease) + exit_client(0); + if (!client_config.foreground) + background(); + + } else if (*message == DHCPNAK) { + /* return to init state */ + LOG(LOG_INFO, "Received DHCP NAK"); + run_script(&packet, "nak"); + if (state != REQUESTING) + run_script(NULL, "deconfig"); + state = INIT_SELECTING; + timeout = now; + requested_ip = 0; + packet_num = 0; + change_mode(LISTEN_RAW); + sleep(3); /* avoid excessive network traffic */ + } + break; + /* case BOUND, RELEASED: - ignore all packets */ + } + } else if (retval > 0 && FD_ISSET(signal_pipe[0], &rfds)) { + if (read(signal_pipe[0], &sig, sizeof(signal)) < 0) { + DEBUG(LOG_ERR, "Could not read signal: %s", + strerror(errno)); + continue; /* probably just EINTR */ + } + switch (sig) { + case SIGUSR1: + perform_renew(); + break; + case SIGUSR2: + perform_release(); + break; + case SIGTERM: + LOG(LOG_INFO, "Received SIGTERM"); + exit_client(0); + } + } else if (retval == -1 && errno == EINTR) { + /* a signal was caught */ + } else { + /* An error occured */ + DEBUG(LOG_ERR, "Error on select"); + } + + } + return 0; +} + -- cgit v1.2.3