/* dhcp.c - DHCP client for dynamic network configuration. * * Copyright 2012 Madhur Verma * Copyright 2013 Kyungwan Han * * Not in SUSv4. USE_DHCP(NEWTOY(dhcp, "V:H:F:x*r:O*A#<0T#<0t#<0s:p:i:SBRCaovqnbf", TOYFLAG_SBIN|TOYFLAG_ROOTONLY)) config DHCP bool "dhcp" default n help usage: dhcp [-fbnqvoCRB] [-i IFACE] [-r IP] [-s PROG] [-p PIDFILE] [-H HOSTNAME] [-V VENDOR] [-x OPT:VAL] [-O OPT] Configure network dynamicaly using DHCP. -i Interface to use (default eth0) -p Create pidfile -s Run PROG at DHCP events (default /usr/share/dhcp/default.script) -B Request broadcast replies -t Send up to N discover packets -T Pause between packets (default 3 seconds) -A Wait N seconds after failure (default 20) -f Run in foreground -b Background if lease is not obtained -n Exit if lease is not obtained -q Exit after obtaining lease -R Release IP on exit -S Log to syslog too -a Use arping to validate offered address -O Request option OPT from server (cumulative) -o Don't request any options (unless -O is given) -r Request this IP address -x OPT:VAL Include option OPT in sent packets (cumulative) -F Ask server to update DNS mapping for NAME -H Send NAME as client hostname (default none) -V VENDOR Vendor identifier (default 'toybox VERSION') -C Don't send MAC as client identifier -v Verbose Signals: USR1 Renew current lease USR2 Release current lease */ #define FOR_dhcp #include "toys.h" // TODO: headers not in posix: #include #include #include #include //FIXME: linux specific. fix for other OS ports #include GLOBALS( char *iface; char *pidfile; char *script; long retries; long timeout; long tryagain; struct arg_list *req_opt; char *req_ip; struct arg_list *pkt_opt; char *fdn_name; char *hostname; char *vendor_cls; ) #define flag_get(f,v,d) ((toys.optflags & f) ? v : d) #define flag_chk(f) ((toys.optflags & f) ? 1 : 0) #define STATE_INIT 0 #define STATE_REQUESTING 1 #define STATE_BOUND 2 #define STATE_RENEWING 3 #define STATE_REBINDING 4 #define STATE_RENEW_REQUESTED 5 #define STATE_RELEASED 6 #define BOOTP_BROADCAST 0x8000 #define DHCP_MAGIC 0x63825363 #define DHCP_REQUEST 1 #define DHCP_REPLY 2 #define DHCP_HTYPE_ETHERNET 1 #define DHCPC_SERVER_PORT 67 #define DHCPC_CLIENT_PORT 68 #define DHCPDISCOVER 1 #define DHCPOFFER 2 #define DHCPREQUEST 3 #define DHCPACK 5 #define DHCPNAK 6 #define DHCPRELEASE 7 #define DHCP_OPTION_PADDING 0x00 #define DHCP_OPTION_SUBNET_MASK 0x01 #define DHCP_OPTION_ROUTER 0x03 #define DHCP_OPTION_DNS_SERVER 0x06 #define DHCP_OPTION_HOST_NAME 0x0c #define DHCP_OPTION_BROADCAST 0x1c #define DHCP_OPTION_REQ_IPADDR 0x32 #define DHCP_OPTION_LEASE_TIME 0x33 #define DHCP_OPTION_OVERLOAD 0x34 #define DHCP_OPTION_MSG_TYPE 0x35 #define DHCP_OPTION_SERVER_ID 0x36 #define DHCP_OPTION_REQ_LIST 0x37 #define DHCP_OPTION_MAX_SIZE 0x39 #define DHCP_OPTION_CLIENTID 0x3D #define DHCP_OPTION_VENDOR 0x3C #define DHCP_OPTION_FQDN 0x51 #define DHCP_OPTION_END 0xFF #define DHCP_NUM8 (1<<8) #define DHCP_NUM16 (1<<9) #define DHCP_NUM32 DHCP_NUM16 | DHCP_NUM8 #define DHCP_STRING (1<<10) #define DHCP_STRLST (1<<11) #define DHCP_IP (1<<12) #define DHCP_IPLIST (1<<13) #define DHCP_IPPLST (1<<14) #define DHCP_STCRTS (1<<15) #define LOG_SILENT 0x0 #define LOG_CONSOLE 0x1 #define LOG_SYSTEM 0x2 #define MODE_OFF 0 #define MODE_RAW 1 #define MODE_APP 2 static void (*dbg)(char *format, ...); static void dummy(char *format, ...){ return; } typedef struct dhcpc_result_s { struct in_addr serverid; struct in_addr ipaddr; struct in_addr netmask; struct in_addr dnsaddr; struct in_addr default_router; uint32_t lease_time; } dhcpc_result_t; typedef struct __attribute__((packed)) dhcp_msg_s { uint8_t op; uint8_t htype; uint8_t hlen; uint8_t hops; uint32_t xid; uint16_t secs; uint16_t flags; uint32_t ciaddr; uint32_t yiaddr; uint32_t nsiaddr; uint32_t ngiaddr; uint8_t chaddr[16]; uint8_t sname[64]; uint8_t file[128]; uint32_t cookie; uint8_t options[308]; } dhcp_msg_t; typedef struct __attribute__((packed)) dhcp_raw_s { struct iphdr iph; struct udphdr udph; dhcp_msg_t dhcp; } dhcp_raw_t; typedef struct dhcpc_state_s { uint8_t macaddr[6]; char *iface; int ifindex; int sockfd; int status; int mode; uint32_t mask; struct in_addr ipaddr; struct in_addr serverid; dhcp_msg_t pdhcp; } dhcpc_state_t; typedef struct option_val_s { char *key; uint16_t code; void *val; size_t len; } option_val_t; struct fd_pair { int rd; int wr; }; static uint32_t xid; static dhcpc_state_t *state; static struct fd_pair sigfd; uint8_t bmacaddr[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; int set = 1; uint8_t infomode = LOG_CONSOLE; uint8_t raw_opt[29]; int raw_optcount = 0; struct arg_list *x_opt; in_addr_t server = 0; static option_val_t *msgopt_list = NULL; static option_val_t options_list[] = { {"lease" , DHCP_NUM32 | 0x33, NULL, 0}, {"subnet" , DHCP_IP | 0x01, NULL, 0}, {"broadcast" , DHCP_IP | 0x1c, NULL, 0}, {"router" , DHCP_IP | 0x03, NULL, 0}, {"ipttl" , DHCP_NUM8 | 0x17, NULL, 0}, {"mtu" , DHCP_NUM16 | 0x1a, NULL, 0}, {"hostname" , DHCP_STRING | 0x0c, NULL, 0}, {"domain" , DHCP_STRING | 0x0f, NULL, 0}, {"search" , DHCP_STRLST | 0x77, NULL, 0}, {"nisdomain" , DHCP_STRING | 0x28, NULL, 0}, {"timezone" , DHCP_NUM32 | 0x02, NULL, 0}, {"tftp" , DHCP_STRING | 0x42, NULL, 0}, {"bootfile" , DHCP_STRING | 0x43, NULL, 0}, {"bootsize" , DHCP_NUM16 | 0x0d, NULL, 0}, {"rootpath" , DHCP_STRING | 0x11, NULL, 0}, {"wpad" , DHCP_STRING | 0xfc, NULL, 0}, {"serverid" , DHCP_IP | 0x36, NULL, 0}, {"message" , DHCP_STRING | 0x38, NULL, 0}, {"vlanid" , DHCP_NUM32 | 0x84, NULL, 0}, {"vlanpriority" , DHCP_NUM32 | 0x85, NULL, 0}, {"dns" , DHCP_IPLIST | 0x06, NULL, 0}, {"wins" , DHCP_IPLIST | 0x2c, NULL, 0}, {"nissrv" , DHCP_IPLIST | 0x29, NULL, 0}, {"ntpsrv" , DHCP_IPLIST | 0x2a, NULL, 0}, {"lprsrv" , DHCP_IPLIST | 0x09, NULL, 0}, {"swapsrv" , DHCP_IP | 0x10, NULL, 0}, {"routes" , DHCP_STCRTS | 0x21, NULL, 0}, {"staticroutes" , DHCP_STCRTS | 0x79, NULL, 0}, {"msstaticroutes" , DHCP_STCRTS | 0xf9, NULL, 0}, }; static struct sock_filter filter_instr[] = { BPF_STMT(BPF_LD|BPF_B|BPF_ABS, 9), BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, IPPROTO_UDP, 0, 6), BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 6), BPF_JUMP(BPF_JMP|BPF_JSET|BPF_K, 0x1fff, 4, 0), BPF_STMT(BPF_LDX|BPF_B|BPF_MSH, 0), BPF_STMT(BPF_LD|BPF_H|BPF_IND, 2), BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 68, 0, 1), BPF_STMT(BPF_RET|BPF_K, 0xffffffff), BPF_STMT(BPF_RET|BPF_K, 0), }; static struct sock_fprog filter_prog = { .len = ARRAY_LEN(filter_instr), .filter = (struct sock_filter *) filter_instr, }; // calculate options size. static int dhcp_opt_size(uint8_t *optionptr) { int i = 0; for(;optionptr[i] != 0xff; i++) if(optionptr[i] != 0x00) i += optionptr[i + 1] + 2 -1; return i; } // calculates checksum for dhcp messages. static uint16_t dhcp_checksum(void *addr, int count) { int32_t sum = 0; uint16_t tmp = 0, *source = (uint16_t *)addr; while (count > 1) { sum += *source++; count -= 2; } if (count > 0) { *(uint8_t*)&tmp = *(uint8_t*)source; sum += tmp; } while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); return ~sum; } // gets information of INTERFACE and updates IFINDEX, MAC and IP static int get_interface( char *interface, int *ifindex, uint32_t *oip, uint8_t *mac) { struct ifreq req; struct sockaddr_in *ip; int fd = xsocket(AF_INET, SOCK_RAW, IPPROTO_RAW); req.ifr_addr.sa_family = AF_INET; xstrncpy(req.ifr_name, interface, IFNAMSIZ); req.ifr_name[IFNAMSIZ-1] = '\0'; xioctl(fd, SIOCGIFFLAGS, &req); if (!(req.ifr_flags & IFF_UP)) return -1; if (oip) { xioctl(fd, SIOCGIFADDR, &req); ip = (struct sockaddr_in*) &req.ifr_addr; dbg("IP %s\n", inet_ntoa(ip->sin_addr)); *oip = ntohl(ip->sin_addr.s_addr); } if (ifindex) { xioctl(fd, SIOCGIFINDEX, &req); dbg("Adapter index %d\n", req.ifr_ifindex); *ifindex = req.ifr_ifindex; } if (mac) { xioctl(fd, SIOCGIFHWADDR, &req); memcpy(mac, req.ifr_hwaddr.sa_data, 6); dbg("MAC %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } close(fd); return 0; } /* *logs messeges to syslog or console *opening the log is still left with applet. *FIXME: move to more relevent lib. probably libc.c */ static void infomsg(uint8_t infomode, char *s, ...) { int used; char *msg; va_list p, t; if (infomode == LOG_SILENT) return; va_start(p, s); va_copy(t, p); used = vsnprintf(NULL, 0, s, t); used++; va_end(t); msg = xmalloc(used); vsnprintf(msg, used, s, p); va_end(p); if (infomode & LOG_SYSTEM) syslog(LOG_INFO, "%s", msg); if (infomode & LOG_CONSOLE) printf("%s\n", msg); free(msg); } /* * Writes self PID in file PATH * FIXME: libc implementation only writes in /var/run * this is more generic as some implemenation may provide * arguments to write in specific file. as dhcpd does. */ static void write_pid(char *path) { int pidfile = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0666); if (pidfile > 0) { char pidbuf[12]; sprintf(pidbuf, "%u", (unsigned)getpid()); write(pidfile, pidbuf, strlen(pidbuf)); close(pidfile); } } // String STR to UINT32 conversion strored in VAR static long strtou32( char *str) { char *endptr = NULL; int base = 10; errno=0; if (str[0]=='0' && (str[1]=='x' || str[1]=='X')) { base = 16; str+=2; } long ret_val = strtol(str, &endptr, base); if (errno) return -1; else if (endptr && (*endptr!='\0'||endptr == str)) return -1; return ret_val; } // IP String STR to binary data. static int striptovar( char *str, void *var) { in_addr_t addr; if(!str) error_exit("NULL address string."); addr = inet_addr(str); if(addr == -1) error_exit("Wrong address %s.",str ); *((uint32_t*)(var)) = (uint32_t)addr; return 0; } // String to dhcp option conversion static int strtoopt( char *str, uint8_t optonly) { char *option, *valstr, *grp, *tp; long optcode = 0, convtmp; uint16_t flag = 0; uint32_t mask, nip, router; int count, size = ARRAY_LEN(options_list); if (!*str) return 0; option = strtok((char*)str, ":"); if (!option) return -1; dbg("-x option : %s ", option); optcode = strtou32(option); if (optcode > 0 && optcode < 256) { // raw option for (count = 0; count < size; count++) { if ((options_list[count].code & 0X00FF) == optcode) { flag = (options_list[count].code & 0XFF00); break; } } if (count == size) error_exit("Obsolete OR Unknown Option : %s", option); } else { // string option for (count = 0; count < size; count++) { if (!strcmp(options_list[count].key, option)) { flag = (options_list[count].code & 0XFF00); optcode = (options_list[count].code & 0X00FF); break; } } if (count == size) error_exit("Obsolete OR Unknown Option : %s", option); } if (!flag || !optcode) return -1; if (optonly) return optcode; valstr = strtok(NULL, "\n"); if (!valstr) error_exit("option %s has no value defined.\n", option); dbg(" value : %-20s \n ", valstr); switch (flag) { case DHCP_NUM32: options_list[count].len = sizeof(uint32_t); options_list[count].val = xmalloc(sizeof(uint32_t)); convtmp = strtou32(valstr); if (convtmp < 0) error_exit("Invalid/wrong formated number %s", valstr); convtmp = htonl(convtmp); memcpy(options_list[count].val, &convtmp, sizeof(uint32_t)); break; case DHCP_NUM16: options_list[count].len = sizeof(uint16_t); options_list[count].val = xmalloc(sizeof(uint16_t)); convtmp = strtou32(valstr); if (convtmp < 0) error_exit("Invalid/malformed number %s", valstr); convtmp = htons(convtmp); memcpy(options_list[count].val, &convtmp, sizeof(uint16_t)); break; case DHCP_NUM8: options_list[count].len = sizeof(uint8_t); options_list[count].val = xmalloc(sizeof(uint8_t)); convtmp = strtou32(valstr); if (convtmp < 0) error_exit("Invalid/malformed number %s", valstr); memcpy(options_list[count].val, &convtmp, sizeof(uint8_t)); break; case DHCP_IP: options_list[count].len = sizeof(uint32_t); options_list[count].val = xmalloc(sizeof(uint32_t)); striptovar(valstr, options_list[count].val); break; case DHCP_STRING: options_list[count].len = strlen(valstr); options_list[count].val = strdup(valstr); break; case DHCP_IPLIST: while(valstr){ options_list[count].val = xrealloc(options_list[count].val, options_list[count].len + sizeof(uint32_t)); striptovar(valstr, ((uint8_t*)options_list[count].val)+options_list[count].len); options_list[count].len += sizeof(uint32_t); valstr = strtok(NULL," \t"); } break; case DHCP_STRLST: case DHCP_IPPLST: break; case DHCP_STCRTS: /* Option binary format: * mask [one byte, 0..32] * ip [0..4 bytes depending on mask] * router [4 bytes] * may be repeated * staticroutes 10.0.0.0/8 10.127.0.1, 10.11.12.0/24 10.11.12.1 */ grp = strtok(valstr, ",");; while(grp){ while(*grp == ' ' || *grp == '\t') grp++; tp = strchr(grp, '/'); if (!tp) error_exit("malformed static route option"); *tp = '\0'; mask = strtol(++tp, &tp, 10); if (striptovar(grp, (uint8_t*)&nip) < 0) error_exit("malformed static route option"); while(*tp == ' ' || *tp == '\t' || *tp == '-') tp++; if (striptovar(tp, (uint8_t*)&router) < 0) error_exit("malformed static route option"); options_list[count].val = xrealloc(options_list[count].val, options_list[count].len + 1 + mask/8 + 4); memcpy(((uint8_t*)options_list[count].val)+options_list[count].len, &mask, 1); options_list[count].len += 1; memcpy(((uint8_t*)options_list[count].val)+options_list[count].len, &nip, mask/8); options_list[count].len += mask/8; memcpy(((uint8_t*)options_list[count].val)+options_list[count].len, &router, 4); options_list[count].len += 4; tp = NULL; grp = strtok(NULL, ","); } break; } return 0; } // Creates environment pointers from RES to use in script static int fill_envp(dhcpc_result_t *res) { struct in_addr temp; int size = ARRAY_LEN(options_list), count, ret = -1; ret = setenv("interface", state->iface, 1); if (!res) return ret; if (res->ipaddr.s_addr) { temp.s_addr = htonl(res->ipaddr.s_addr); ret = setenv("ip", inet_ntoa(temp), 1); if (ret) return ret; } if (msgopt_list) { for (count = 0; count < size; count++) { if ((msgopt_list[count].len == 0) || (msgopt_list[count].val == NULL)) continue; ret = setenv(msgopt_list[count].key, (char*)msgopt_list[count].val, 1); if (ret) return ret; } } return ret; } // Executes Script NAME. static void run_script(dhcpc_result_t *res, char *name) { volatile int error = 0; pid_t pid; char *argv[3]; struct stat sts; char *script = flag_get(FLAG_s, TT.script, "/usr/share/dhcp/default.script"); if (stat(script, &sts) == -1 && errno == ENOENT) return; if (fill_envp(res)) { dbg("Failed to create environment variables."); return; } dbg("Executing %s %s\n", script, name); argv[0] = (char*) script; argv[1] = (char*) name; argv[2] = NULL; fflush(NULL); pid = vfork(); if (pid < 0) { dbg("Fork failed.\n"); return; } if (!pid) { execvp(argv[0], argv); error = errno; _exit(111); } if (error) { waitpid(pid, NULL,0); errno = error; perror_msg("script exec failed"); } dbg("script complete.\n"); } // returns a randome ID static uint32_t getxid(void) { uint32_t randnum; int fd = xopen("/dev/urandom", O_RDONLY); xreadall(fd, &randnum, sizeof(randnum)); xclose(fd); return randnum; } // opens socket in raw mode. static int mode_raw(void) { state->mode = MODE_OFF; struct sockaddr_ll sock; if (state->sockfd > 0) close(state->sockfd); dbg("Opening raw socket on ifindex %d\n", state->ifindex); state->sockfd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); if (state->sockfd < 0) { dbg("MODE RAW : socket fail ERROR : %d\n", state->sockfd); return -1; } dbg("Got raw socket fd %d\n", state->sockfd); memset(&sock, 0, sizeof(sock)); sock.sll_family = AF_PACKET; sock.sll_protocol = htons(ETH_P_IP); sock.sll_ifindex = state->ifindex; if (bind(state->sockfd, (struct sockaddr *) &sock, sizeof(sock))) { dbg("MODE RAW : bind fail.\n"); close(state->sockfd); return -1; } state->mode = MODE_RAW; if (setsockopt(state->sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog, sizeof(filter_prog)) < 0) dbg("MODE RAW : filter attach fail.\n"); dbg("MODE RAW : success\n"); return 0; } // opens UDP socket static int mode_app(void) { struct sockaddr_in addr; struct ifreq ifr; state->mode = MODE_OFF; if (state->sockfd > 0) close(state->sockfd); dbg("Opening listen socket on *:%d %s\n", DHCPC_CLIENT_PORT, state->iface); state->sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (state->sockfd < 0) { dbg("MODE APP : socket fail ERROR: %d\n", state->sockfd); return -1; } setsockopt(state->sockfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set)); if (setsockopt(state->sockfd, SOL_SOCKET, SO_BROADCAST, &set, sizeof(set)) == -1) { dbg("MODE APP : brodcast failed.\n"); close(state->sockfd); return -1; } xstrncpy(ifr.ifr_name, state->iface, IFNAMSIZ); ifr.ifr_name[IFNAMSIZ -1] = '\0'; setsockopt(state->sockfd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)); memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(DHCPC_CLIENT_PORT); addr.sin_addr.s_addr = INADDR_ANY ; if (bind(state->sockfd, (struct sockaddr *) &addr, sizeof(addr))) { close(state->sockfd); dbg("MODE APP : bind failed.\n"); return -1; } state->mode = MODE_APP; dbg("MODE APP : success\n"); return 0; } static int read_raw(void) { dhcp_raw_t packet; uint16_t check; int bytes = 0; memset(&packet, 0, sizeof(packet)); if ((bytes = read(state->sockfd, &packet, sizeof(packet))) < 0) { dbg("\tPacket read error, ignoring\n"); return bytes; } if (bytes < (int) (sizeof(packet.iph) + sizeof(packet.udph))) { dbg("\tPacket is too short, ignoring\n"); return -2; } if (bytes < ntohs(packet.iph.tot_len)) { dbg("\tOversized packet, ignoring\n"); return -2; } // ignore any extra garbage bytes bytes = ntohs(packet.iph.tot_len); // make sure its the right packet for us, and that it passes sanity checks if (packet.iph.protocol != IPPROTO_UDP || packet.iph.version != IPVERSION || packet.iph.ihl != (sizeof(packet.iph) >> 2) || packet.udph.dest != htons(DHCPC_CLIENT_PORT) || ntohs(packet.udph.len) != (uint16_t)(bytes - sizeof(packet.iph))) { dbg("\tUnrelated/bogus packet, ignoring\n"); return -2; } // verify IP checksum check = packet.iph.check; packet.iph.check = 0; if (check != dhcp_checksum(&packet.iph, sizeof(packet.iph))) { dbg("\tBad IP header checksum, ignoring\n"); return -2; } memset(&packet.iph, 0, ((size_t) &((struct iphdr *)0)->protocol)); packet.iph.tot_len = packet.udph.len; check = packet.udph.check; packet.udph.check = 0; if (check && check != dhcp_checksum(&packet, bytes)) { dbg("\tPacket with bad UDP checksum received, ignoring\n"); return -2; } memcpy(&state->pdhcp, &packet.dhcp, bytes - (sizeof(packet.iph) + sizeof(packet.udph))); if (state->pdhcp.cookie != htonl(DHCP_MAGIC)) { dbg("\tPacket with bad magic, ignoring\n"); return -2; } return bytes - sizeof(packet.iph) - sizeof(packet.udph); } static int read_app(void) { int ret; memset(&state->pdhcp, 0, sizeof(dhcp_msg_t)); if ((ret = read(state->sockfd, &state->pdhcp, sizeof(dhcp_msg_t))) < 0) { dbg("Packet read error, ignoring\n"); return ret; /* returns -1 */ } if (state->pdhcp.cookie != htonl(DHCP_MAGIC)) { dbg("Packet with bad magic, ignoring\n"); return -2; } return ret; } // Sends data through raw socket. static int send_raw(void) { struct sockaddr_ll dest_sll; dhcp_raw_t packet; unsigned padding; int fd, result = -1; memset(&packet, 0, sizeof(dhcp_raw_t)); memcpy(&packet.dhcp, &state->pdhcp, sizeof(dhcp_msg_t)); if ((fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { dbg("SEND RAW: socket failed\n"); return result; } memset(&dest_sll, 0, sizeof(dest_sll)); dest_sll.sll_family = AF_PACKET; dest_sll.sll_protocol = htons(ETH_P_IP); dest_sll.sll_ifindex = state->ifindex; dest_sll.sll_halen = 6; memcpy(dest_sll.sll_addr, bmacaddr , 6); if (bind(fd, (struct sockaddr *) &dest_sll, sizeof(dest_sll)) < 0) { dbg("SEND RAW: bind failed\n"); close(fd); return result; } padding = 308 - 1 - dhcp_opt_size(state->pdhcp.options); packet.iph.protocol = IPPROTO_UDP; packet.iph.saddr = INADDR_ANY; packet.iph.daddr = INADDR_BROADCAST; packet.udph.source = htons(DHCPC_CLIENT_PORT); packet.udph.dest = htons(DHCPC_SERVER_PORT); packet.udph.len = htons(sizeof(dhcp_raw_t) - sizeof(struct iphdr) - padding); packet.iph.tot_len = packet.udph.len; packet.udph.check = dhcp_checksum(&packet, sizeof(dhcp_raw_t) - padding); packet.iph.tot_len = htons(sizeof(dhcp_raw_t) - padding); packet.iph.ihl = sizeof(packet.iph) >> 2; packet.iph.version = IPVERSION; packet.iph.ttl = IPDEFTTL; packet.iph.check = dhcp_checksum(&packet.iph, sizeof(packet.iph)); result = sendto(fd, &packet, sizeof(dhcp_raw_t) - padding, 0, (struct sockaddr *) &dest_sll, sizeof(dest_sll)); close(fd); if (result < 0) dbg("SEND RAW: PACKET send error\n"); return result; } // Sends data through UDP socket. static int send_app(void) { struct sockaddr_in cli; int fd, ret = -1; if ((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { dbg("SEND APP: sock failed.\n"); return ret; } setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set)); memset(&cli, 0, sizeof(cli)); cli.sin_family = AF_INET; cli.sin_port = htons(DHCPC_CLIENT_PORT); cli.sin_addr.s_addr = state->pdhcp.ciaddr; if (bind(fd, (struct sockaddr *)&cli, sizeof(cli)) == -1) { dbg("SEND APP: bind failed.\n"); goto error_fd; } memset(&cli, 0, sizeof(cli)); cli.sin_family = AF_INET; cli.sin_port = htons(DHCPC_SERVER_PORT); cli.sin_addr.s_addr = state->serverid.s_addr; if (connect(fd, (struct sockaddr *)&cli, sizeof(cli)) == -1) { dbg("SEND APP: connect failed.\n"); goto error_fd; } int padding = 308 - 1 - dhcp_opt_size(state->pdhcp.options); if((ret = write(fd, &state->pdhcp, sizeof(dhcp_msg_t) - padding)) < 0) { dbg("SEND APP: write failed error %d\n", ret); goto error_fd; } dbg("SEND APP: write success wrote %d\n", ret); error_fd: close(fd); return ret; } // Generic signal handler real handling is done in main funcrion. static void signal_handler(int sig) { unsigned char ch = sig; if (write(sigfd.wr, &ch, 1) != 1) dbg("can't send signal\n"); } // signal setup for SIGUSR1 SIGUSR2 SIGTERM static int setup_signal() { if (pipe((int *)&sigfd) < 0) { dbg("signal pipe failed\n"); return -1; } fcntl(sigfd.wr , F_SETFD, FD_CLOEXEC); fcntl(sigfd.rd , F_SETFD, FD_CLOEXEC); int flags = fcntl(sigfd.wr, F_GETFL); fcntl(sigfd.wr, F_SETFL, flags | O_NONBLOCK); signal(SIGUSR1, signal_handler); signal(SIGUSR2, signal_handler); signal(SIGTERM, signal_handler); return 0; } // adds client id to dhcp packet static uint8_t *dhcpc_addclientid(uint8_t *optptr) { *optptr++ = DHCP_OPTION_CLIENTID; *optptr++ = 7; *optptr++ = 1; memcpy(optptr, &state->macaddr, 6); return optptr + 6; } // adds messege type to dhcp packet static uint8_t *dhcpc_addmsgtype(uint8_t *optptr, uint8_t type) { *optptr++ = DHCP_OPTION_MSG_TYPE; *optptr++ = 1; *optptr++ = type; return optptr; } // adds max size to dhcp packet static uint8_t *dhcpc_addmaxsize(uint8_t *optptr, uint16_t size) { *optptr++ = DHCP_OPTION_MAX_SIZE; *optptr++ = 2; memcpy(optptr, &size, 2); return optptr + 2; } static uint8_t *dhcpc_addstropt(uint8_t *optptr, uint8_t opcode, char* str, int len) { *optptr++ = opcode; *optptr++ = len; memcpy(optptr, str, len); return optptr + len; } // adds server id to dhcp packet. static uint8_t *dhcpc_addserverid(struct in_addr *serverid, uint8_t *optptr) { *optptr++ = DHCP_OPTION_SERVER_ID; *optptr++ = 4; memcpy(optptr, &serverid->s_addr, 4); return optptr + 4; } // adds requested ip address to dhcp packet. static uint8_t *dhcpc_addreqipaddr(struct in_addr *ipaddr, uint8_t *optptr) { *optptr++ = DHCP_OPTION_REQ_IPADDR; *optptr++ = 4; memcpy(optptr, &ipaddr->s_addr, 4); return optptr + 4; } // adds hostname to dhcp packet. static uint8_t *dhcpc_addfdnname(uint8_t *optptr, char *hname) { int size = strlen(hname); *optptr++ = DHCP_OPTION_FQDN; *optptr++ = size + 3; *optptr++ = 0x1; //flags optptr += 2; // two blank bytes strcpy((char*)optptr, hname); // name return optptr + size; } // adds request options using -o,-O flag to dhcp packet static uint8_t *dhcpc_addreqoptions(uint8_t *optptr) { uint8_t *len; *optptr++ = DHCP_OPTION_REQ_LIST; len = optptr; *len = 0; optptr++; if (!flag_chk(FLAG_o)) { *len = 4; *optptr++ = DHCP_OPTION_SUBNET_MASK; *optptr++ = DHCP_OPTION_ROUTER; *optptr++ = DHCP_OPTION_DNS_SERVER; *optptr++ = DHCP_OPTION_BROADCAST; } if (flag_chk(FLAG_O)) { memcpy(optptr++, raw_opt, raw_optcount); *len += raw_optcount; } return optptr; } static uint8_t *dhcpc_addend(uint8_t *optptr) { *optptr++ = DHCP_OPTION_END; return optptr; } // Sets values of -x options in dhcp discover and request packet. static uint8_t* set_xopt(uint8_t *optptr) { int count; int size = ARRAY_LEN(options_list); for (count = 0; count < size; count++) { if ((options_list[count].len == 0) || (options_list[count].val == NULL)) continue; *optptr++ = (uint8_t) (options_list[count].code & 0x00FF); *optptr++ = (uint8_t) options_list[count].len; memcpy(optptr, options_list[count].val, options_list[count].len); optptr += options_list[count].len; } return optptr; } static uint32_t get_option_serverid (uint8_t *opt, dhcpc_result_t *presult) { uint32_t var = 0; while (*opt != DHCP_OPTION_SERVER_ID) { if (*opt == DHCP_OPTION_END) return var; opt += opt[1] + 2; } memcpy(&var, opt+2, sizeof(uint32_t)); state->serverid.s_addr = var; presult->serverid.s_addr = state->serverid.s_addr; presult->serverid.s_addr = ntohl(presult->serverid.s_addr); return var; } static uint8_t get_option_msgtype(uint8_t *opt) { uint32_t var = 0; while (*opt != DHCP_OPTION_MSG_TYPE) { if (*opt == DHCP_OPTION_END) return var; opt += opt[1] + 2; } memcpy(&var, opt+2, sizeof(uint8_t)); return var; } static uint8_t get_option_lease(uint8_t *opt, dhcpc_result_t *presult) { uint32_t var = 0; while (*opt != DHCP_OPTION_LEASE_TIME) { if (*opt == DHCP_OPTION_END) return var; opt += opt[1] + 2; } memcpy(&var, opt+2, sizeof(uint32_t)); var = htonl(var); presult->lease_time = var; return var; } // sends dhcp msg of MSGTYPE static int dhcpc_sendmsg(int msgtype) { uint8_t *pend; struct in_addr rqsd; char *vendor; // Create the common message header settings memset(&state->pdhcp, 0, sizeof(dhcp_msg_t)); state->pdhcp.op = DHCP_REQUEST; state->pdhcp.htype = DHCP_HTYPE_ETHERNET; state->pdhcp.hlen = 6; state->pdhcp.xid = xid; memcpy(state->pdhcp.chaddr, state->macaddr, 6); memset(&state->pdhcp.chaddr[6], 0, 10); state->pdhcp.cookie = htonl(DHCP_MAGIC);; // Add the common header options pend = state->pdhcp.options; pend = dhcpc_addmsgtype(pend, msgtype); if (!flag_chk(FLAG_C)) pend = dhcpc_addclientid(pend); // Handle the message specific settings switch (msgtype) { case DHCPDISCOVER: // Broadcast DISCOVER message to all servers state->pdhcp.flags = htons(BOOTP_BROADCAST); // Broadcast bit. if (flag_chk(FLAG_r)) { inet_aton(TT.req_ip, &rqsd); pend = dhcpc_addreqipaddr(&rqsd, pend); } pend = dhcpc_addmaxsize(pend, htons(sizeof(dhcp_raw_t))); vendor = flag_get(FLAG_V, TT.vendor_cls, "toybox\0"); pend = dhcpc_addstropt(pend, DHCP_OPTION_VENDOR, vendor, strlen(vendor)); if (flag_chk(FLAG_H)) pend = dhcpc_addstropt(pend, DHCP_OPTION_HOST_NAME, TT.hostname, strlen(TT.hostname)); if (flag_chk(FLAG_F)) pend = dhcpc_addfdnname(pend, TT.fdn_name); if ((!flag_chk(FLAG_o)) || flag_chk(FLAG_O)) pend = dhcpc_addreqoptions(pend); if (flag_chk(FLAG_x)) pend = set_xopt(pend); break; case DHCPREQUEST: // Send REQUEST message to the server that sent the *first* OFFER state->pdhcp.flags = htons(BOOTP_BROADCAST); // Broadcast bit. if (state->status == STATE_RENEWING) memcpy(&state->pdhcp.ciaddr, &state->ipaddr.s_addr, 4); pend = dhcpc_addmaxsize(pend, htons(sizeof(dhcp_raw_t))); rqsd.s_addr = htonl(server); pend = dhcpc_addserverid(&rqsd, pend); pend = dhcpc_addreqipaddr(&state->ipaddr, pend); vendor = flag_get(FLAG_V, TT.vendor_cls, "toybox\0"); pend = dhcpc_addstropt(pend, DHCP_OPTION_VENDOR, vendor, strlen(vendor)); if (flag_chk(FLAG_H)) pend = dhcpc_addstropt(pend, DHCP_OPTION_HOST_NAME, TT.hostname, strlen(TT.hostname)); if (flag_chk(FLAG_F)) pend = dhcpc_addfdnname(pend, TT.fdn_name); if ((!flag_chk(FLAG_o)) || flag_chk(FLAG_O)) pend = dhcpc_addreqoptions(pend); if (flag_chk(FLAG_x)) pend = set_xopt(pend); break; case DHCPRELEASE: // Send RELEASE message to the server. memcpy(&state->pdhcp.ciaddr, &state->ipaddr.s_addr, 4); rqsd.s_addr = htonl(server); pend = dhcpc_addserverid(&rqsd, pend); break; default: return -1; } pend = dhcpc_addend(pend); if (state->mode == MODE_APP) return send_app(); return send_raw(); } /* * parses options from received dhcp packet at OPTPTR and * stores result in PRESULT or MSGOPT_LIST */ static uint8_t dhcpc_parseoptions(dhcpc_result_t *presult, uint8_t *optptr) { uint8_t type = 0, *options, overloaded = 0;; uint16_t flag = 0; uint32_t convtmp = 0; char *dest, *pfx; struct in_addr addr; int count, optlen, size = ARRAY_LEN(options_list); if (flag_chk(FLAG_x)) { if(msgopt_list){ for (count = 0; count < size; count++){ if(msgopt_list[count].val) free(msgopt_list[count].val); msgopt_list[count].val = NULL; msgopt_list[count].len = 0; } } else { msgopt_list = xmalloc(sizeof(options_list)); memcpy(msgopt_list, options_list, sizeof(options_list)); for (count = 0; count < size; count++) { msgopt_list[count].len = 0; msgopt_list[count].val = NULL; } } } else { msgopt_list = options_list; for (count = 0; count < size; count++) { msgopt_list[count].len = 0; if(msgopt_list[count].val) free(msgopt_list[count].val); msgopt_list[count].val = NULL; } } while (*optptr != DHCP_OPTION_END) { if (*optptr == DHCP_OPTION_PADDING) { optptr++; continue; } if (*optptr == DHCP_OPTION_OVERLOAD) { overloaded = optptr[2]; optptr += optptr[1] + 2; continue; } for (count = 0, flag = 0; count < size; count++) { if ((msgopt_list[count].code & 0X00FF) == *optptr) { flag = (msgopt_list[count].code & 0XFF00); break; } } switch (flag) { case DHCP_NUM32: memcpy(&convtmp, &optptr[2], sizeof(uint32_t)); convtmp = htonl(convtmp); sprintf(toybuf, "%u", convtmp); msgopt_list[count].val = strdup(toybuf); msgopt_list[count].len = strlen(toybuf); break; case DHCP_NUM16: memcpy(&convtmp, &optptr[2], sizeof(uint16_t)); convtmp = htons(convtmp); sprintf(toybuf, "%u", convtmp); msgopt_list[count].val = strdup(toybuf); msgopt_list[count].len = strlen(toybuf); break; case DHCP_NUM8: memcpy(&convtmp, &optptr[2], sizeof(uint8_t)); sprintf(toybuf, "%u", convtmp); msgopt_list[count].val = strdup(toybuf); msgopt_list[count].len = strlen(toybuf); break; case DHCP_IP: memcpy(&convtmp, &optptr[2], sizeof(uint32_t)); addr.s_addr = convtmp; sprintf(toybuf, "%s", inet_ntoa(addr)); msgopt_list[count].val = strdup(toybuf); msgopt_list[count].len = strlen(toybuf); break; case DHCP_STRING: sprintf(toybuf, "%.*s", optptr[1], &optptr[2]); msgopt_list[count].val = strdup(toybuf); msgopt_list[count].len = strlen(toybuf); break; case DHCP_IPLIST: optlen = optptr[1]; dest = toybuf; while (optlen) { memcpy(&convtmp, &optptr[2], sizeof(uint32_t)); addr.s_addr = convtmp; dest += sprintf(dest, "%s ", inet_ntoa(addr)); optlen -= 4; } *(dest - 1) = '\0'; msgopt_list[count].val = strdup(toybuf); msgopt_list[count].len = strlen(toybuf); break; case DHCP_STRLST: //FIXME: do smthing. case DHCP_IPPLST: break; case DHCP_STCRTS: pfx = ""; dest = toybuf; options = &optptr[2]; optlen = optptr[1]; while (optlen >= 1 + 4) { uint32_t nip = 0; int bytes; uint8_t *p_tmp; unsigned mask = *options; if (mask > 32) break; optlen--; p_tmp = (void*) &nip; bytes = (mask + 7) / 8; while (--bytes >= 0) { *p_tmp++ = *options++; optlen--; } if (optlen < 4) break; dest += sprintf(dest, "%s%u.%u.%u.%u", pfx, ((uint8_t*) &nip)[0], ((uint8_t*) &nip)[1], ((uint8_t*) &nip)[2], ((uint8_t*) &nip)[3]); pfx = " "; dest += sprintf(dest, "/%u ", mask); dest += sprintf(dest, "%u.%u.%u.%u", options[0], options[1], options[2], options[3]); options += 4; optlen -= 4; } msgopt_list[count].val = strdup(toybuf); msgopt_list[count].len = strlen(toybuf); break; default: break; } optptr += optptr[1] + 2; } if ((overloaded == 1) || (overloaded == 3)) dhcpc_parseoptions(presult, optptr); if ((overloaded == 2) || (overloaded == 3)) dhcpc_parseoptions(presult, optptr); return type; } // parses recvd messege to check that it was for us. static uint8_t dhcpc_parsemsg(dhcpc_result_t *presult) { if (state->pdhcp.op == DHCP_REPLY && !memcmp(state->pdhcp.chaddr, state->macaddr, 6) && !memcmp(&state->pdhcp.xid, &xid, sizeof(xid))) { memcpy(&presult->ipaddr.s_addr, &state->pdhcp.yiaddr, 4); presult->ipaddr.s_addr = ntohl(presult->ipaddr.s_addr); return get_option_msgtype(state->pdhcp.options); } return 0; } // Sends a IP renew request. static void renew(void) { infomsg(infomode, "Performing a DHCP renew"); switch (state->status) { case STATE_INIT: break; case STATE_BOUND: mode_raw(); case STATE_RENEWING: // FALLTHROUGH case STATE_REBINDING: // FALLTHROUGH state->status = STATE_RENEW_REQUESTED; break; case STATE_RENEW_REQUESTED: run_script(NULL, "deconfig"); case STATE_REQUESTING: // FALLTHROUGH case STATE_RELEASED: // FALLTHROUGH mode_raw(); state->status = STATE_INIT; break; default: break; } } // Sends a IP release request. static void release(void) { int len = sizeof("255.255.255.255\0"); char buffer[len]; struct in_addr temp_addr; mode_app(); // send release packet if (state->status == STATE_BOUND || state->status == STATE_RENEWING || state->status == STATE_REBINDING) { temp_addr.s_addr = htonl(server); strncpy(buffer, inet_ntoa(temp_addr), sizeof(buffer)); buffer[len - 1] = '\0'; temp_addr.s_addr = state->ipaddr.s_addr; infomsg( infomode, "Unicasting a release of %s to %s", inet_ntoa(temp_addr), buffer); dhcpc_sendmsg(DHCPRELEASE); run_script(NULL, "deconfig"); } infomsg(infomode, "Entering released state"); close(state->sockfd); state->sockfd = -1; state->mode = MODE_OFF; state->status = STATE_RELEASED; } static void free_option_stores(void) { int count, size = ARRAY_LEN(options_list); for (count = 0; count < size; count++) if (options_list[count].val) free(options_list[count].val); if(flag_chk(FLAG_x)){ for (count = 0; count < size; count++) if (msgopt_list[count].val) free(msgopt_list[count].val); free(msgopt_list); } } void dhcp_main(void) { struct timeval tv; int retval, bufflen = 0; dhcpc_result_t result; uint8_t packets = 0, retries = 0; uint32_t timeout = 0, waited = 0; fd_set rfds; xid = 0; setlinebuf(stdout); dbg = dummy; if (flag_chk(FLAG_v)) dbg = xprintf; if (flag_chk(FLAG_p)) write_pid(TT.pidfile); retries = flag_get(FLAG_t, TT.retries, 3); if (flag_chk(FLAG_S)) { openlog("UDHCPC :", LOG_PID, LOG_DAEMON); infomode |= LOG_SYSTEM; } infomsg(infomode, "dhcp started"); if (flag_chk(FLAG_O)) { while (TT.req_opt) { raw_opt[raw_optcount] = (uint8_t) strtoopt(TT.req_opt->arg, 1); raw_optcount++; TT.req_opt = TT.req_opt->next; } } if (flag_chk(FLAG_x)) { while (TT.pkt_opt) { (void) strtoopt(TT.pkt_opt->arg, 0); TT.pkt_opt = TT.pkt_opt->next; } } memset(&result, 0, sizeof(dhcpc_result_t)); state = (dhcpc_state_t*) xmalloc(sizeof(dhcpc_state_t)); memset(state, 0, sizeof(dhcpc_state_t)); state->iface = flag_get(FLAG_i, TT.iface, "eth0"); if (get_interface(state->iface, &state->ifindex, NULL, state->macaddr)) perror_exit("Failed to get interface %s", state->iface); run_script(NULL, "deconfig"); setup_signal(); state->status = STATE_INIT; mode_raw(); fcntl(state->sockfd, F_SETFD, FD_CLOEXEC); for (;;) { FD_ZERO(&rfds); if (state->sockfd >= 0) FD_SET(state->sockfd, &rfds); FD_SET(sigfd.rd, &rfds); tv.tv_sec = timeout - waited; tv.tv_usec = 0; retval = 0; int maxfd = (sigfd.rd > state->sockfd)? sigfd.rd : state->sockfd; dbg("select wait ....\n"); uint32_t timestmp = time(NULL); if((retval = select(maxfd + 1, &rfds, NULL, NULL, &tv)) < 0) { if (errno == EINTR) { waited += (unsigned) time(NULL) - timestmp; continue; } perror_exit("Error in select"); } if (!retval) { // Timed out if (get_interface(state->iface, &state->ifindex, NULL, state->macaddr)) error_exit("Interface lost %s\n", state->iface); switch (state->status) { case STATE_INIT: if (packets < retries) { if (!packets) xid = getxid(); run_script(NULL, "deconfig"); infomsg(infomode, "Sending discover..."); dhcpc_sendmsg(DHCPDISCOVER); server = 0; timeout = flag_get(FLAG_T, TT.timeout, 3); waited = 0; packets++; continue; } lease_fail: run_script(NULL,"leasefail"); if (flag_chk(FLAG_n)) { infomsg(infomode, "Lease failed. Exiting"); goto ret_with_sockfd; } if (flag_chk(FLAG_b)) { infomsg(infomode, "Lease failed. Going Daemon mode"); daemon(0, 0); if (flag_chk(FLAG_p)) write_pid(TT.pidfile); toys.optflags &= ~FLAG_b; toys.optflags |= FLAG_f; } timeout = flag_get(FLAG_A, TT.tryagain, 20); waited = 0; packets = 0; continue; case STATE_REQUESTING: if (packets < retries) { memcpy(&state->ipaddr.s_addr,&state->pdhcp.yiaddr, 4); dhcpc_sendmsg(DHCPREQUEST); infomsg(infomode, "Sending select for %d.%d.%d.%d...", (result.ipaddr.s_addr >> 24) & 0xff, (result.ipaddr.s_addr >> 16) & 0xff, (result.ipaddr.s_addr >> 8) & 0xff, (result.ipaddr.s_addr) & 0xff); timeout = flag_get(FLAG_T, TT.timeout, 3); waited = 0; packets++; continue; } mode_raw(); state->status = STATE_INIT; goto lease_fail; case STATE_BOUND: state->status = STATE_RENEWING; dbg("Entering renew state\n"); // FALLTHROUGH case STATE_RENEW_REQUESTED: // FALLTHROUGH case STATE_RENEWING: renew_requested: if (timeout > 60) { dhcpc_sendmsg(DHCPREQUEST); timeout >>= 1; waited = 0; continue; } dbg("Entering rebinding state\n"); state->status = STATE_REBINDING; // FALLTHROUGH case STATE_REBINDING: mode_raw(); if (timeout > 0) { dhcpc_sendmsg(DHCPREQUEST); timeout >>= 1; waited = 0; continue; } infomsg(infomode, "Lease lost, entering INIT state"); run_script(NULL, "deconfig"); state->status = STATE_INIT; timeout = 0; waited = 0; packets = 0; continue; default: break; } timeout = INT_MAX; waited = 0; continue; } if (FD_ISSET(sigfd.rd, &rfds)) { // Some Activity on RDFDs : is signal unsigned char sig; if (read(sigfd.rd, &sig, 1) != 1) { dbg("signal read failed.\n"); continue; } switch (sig) { case SIGUSR1: infomsg(infomode, "Received SIGUSR1"); renew(); packets = 0; waited = 0; if (state->status == STATE_RENEW_REQUESTED) goto renew_requested; if (state->status == STATE_INIT) timeout = 0; continue; case SIGUSR2: infomsg(infomode, "Received SIGUSR2"); release(); timeout = INT_MAX; waited = 0; packets = 0; continue; case SIGTERM: infomsg(infomode, "Received SIGTERM"); if (flag_chk(FLAG_R)) release(); goto ret_with_sockfd; default: break; } } if (FD_ISSET(state->sockfd, &rfds)) { // Some Activity on RDFDs : is socket dbg("main sock read\n"); uint8_t msgType; if (state->mode == MODE_RAW) bufflen = read_raw(); if (state->mode == MODE_APP) bufflen = read_app(); if (bufflen < 0) { if (state->mode == MODE_RAW) mode_raw(); if (state->mode == MODE_APP) mode_app(); continue; } waited += time(NULL) - timestmp; memset(&result, 0, sizeof(dhcpc_result_t)); msgType = dhcpc_parsemsg(&result); if (msgType != DHCPNAK && result.ipaddr.s_addr == 0 ) continue; // no ip for me ignore if (!msgType || !get_option_serverid(state->pdhcp.options, &result)) continue; //no server id ignore if (msgType == DHCPOFFER && server == 0) server = result.serverid.s_addr; // select the server if (result.serverid.s_addr != server) continue; // not from the server we requested ignore dhcpc_parseoptions(&result, state->pdhcp.options); get_option_lease(state->pdhcp.options, &result); switch (state->status) { case STATE_INIT: if (msgType == DHCPOFFER) { state->status = STATE_REQUESTING; mode_raw(); timeout = 0; waited = 0; packets = 0; } continue; case STATE_REQUESTING: // FALLTHROUGH case STATE_RENEWING: // FALLTHROUGH case STATE_RENEW_REQUESTED: // FALLTHROUGH case STATE_REBINDING: if (msgType == DHCPACK) { timeout = result.lease_time / 2; run_script(&result, state->status == STATE_REQUESTING ? "bound" : "renew"); state->status = STATE_BOUND; infomsg(infomode, "Lease of %d.%d.%d.%d obtained, lease time %d from server %d.%d.%d.%d", (result.ipaddr.s_addr >> 24) & 0xff, (result.ipaddr.s_addr >> 16) & 0xff, (result.ipaddr.s_addr >> 8) & 0xff, (result.ipaddr.s_addr) & 0xff, result.lease_time, (result.serverid.s_addr >> 24) & 0xff, (result.serverid.s_addr >> 16) & 0xff, (result.serverid.s_addr >> 8) & 0xff, (result.serverid.s_addr) & 0xff); if (flag_chk(FLAG_q)) { if (flag_chk(FLAG_R)) release(); goto ret_with_sockfd; } toys.optflags &= ~FLAG_n; if (!flag_chk(FLAG_f)) { daemon(0, 0); toys.optflags |= FLAG_f; if (flag_chk(FLAG_p)) write_pid(TT.pidfile); } waited = 0; continue; } else if (msgType == DHCPNAK) { dbg("NACK received.\n"); run_script(&result, "nak"); if (state->status != STATE_REQUESTING) run_script(NULL, "deconfig"); mode_raw(); sleep(3); state->status = STATE_INIT; state->ipaddr.s_addr = 0; server = 0; timeout = 0; packets = 0; waited = 0; } continue; default: break; } } } ret_with_sockfd: if (CFG_TOYBOX_FREE) { free_option_stores(); if (state->sockfd > 0) close(state->sockfd); free(state); } }