├── .gitignore ├── README.md ├── sdhcp-script ├── config.mk ├── LICENSE ├── Makefile ├── compat.h ├── sdhcp.1 ├── compat.c └── sdhcp.c /.gitignore: -------------------------------------------------------------------------------- 1 | sdhcp 2 | *.o 3 | *.a 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sdhcp 2 | 3 | This is a fork of the suckless.org dhcp client. 4 | 5 | https://core.suckless.org/sdhcp/ 6 | 7 | This version has several changes: 8 | 9 | * raw socket implementation for Linux 10 | * expanded callback (more environment variables) 11 | * support for DUID client id 12 | * support for more servers 13 | * provided sdhcp-script for debugging 14 | -------------------------------------------------------------------------------- /sdhcp-script: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | LOG=/tmp/sdhcp.log 4 | 5 | date >> $LOG 6 | echo "State $STATE" >> $LOG 7 | echo "Ifname $IFNAME" >> $LOG 8 | echo "SPID $SPID" >> $LOG 9 | echo "Lease $LEASE" >> $LOG 10 | echo "Client $CLIENT" >> $LOG 11 | echo "Server $SERVER" >> $LOG 12 | echo "Router $ROUTER" >> $LOG 13 | echo "Mask $MASK" >> $LOG 14 | echo "Domain $DOMAIN" >> $LOG 15 | echo "DNS $DNS" >> $LOG 16 | echo "DNS2 $DNS2" >> $LOG 17 | echo "NTP $NTP" >> $LOG 18 | echo "NTP2 $NTP2" >> $LOG 19 | echo >> $LOG 20 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # sdhcp version 2 | VERSION = 0.1 3 | 4 | PREFIX = /usr/local 5 | DESTDIR = 6 | MANPREFIX = $(PREFIX)/share/man 7 | 8 | CC = cc 9 | LD = $(CC) 10 | CPPFLAGS = -D_DEFAULT_SOURCE 11 | CFLAGS = -Wall -Wextra -pedantic -std=c99 $(CPPFLAGS) 12 | LDFLAGS = -s 13 | 14 | SYS = $(shell uname -s) 15 | 16 | ifeq ($(SYS), QNX) 17 | # QNX will not compile with -std=c99 18 | CFLAGS = -Wall -Wextra -pedantic $(CPPFLAGS) 19 | LDFLAGS += -lsocket 20 | endif 21 | 22 | ifeq ($(findstring BSD, $(SYS)), BSD) 23 | # Needed on BSD for timer_* 24 | LDFLAGS += -lrt 25 | endif 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2012 David Galos (galosd83 (at) students.rowan.edu) 4 | © 2014-2015 Hiltjo Posthuma 5 | © 2015 Michael Forney 6 | © 2019-2020 Sean MacLennan 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include config.mk 2 | 3 | .POSIX: 4 | .SUFFIXES: .c .o 5 | 6 | HDR = compat.h 7 | 8 | SRC = sdhcp.c compat.c 9 | 10 | OBJ = $(SRC:.c=.o) 11 | BIN = sdhcp 12 | MAN = $(BIN).1 13 | 14 | all: options $(BIN) 15 | 16 | options: 17 | @echo sdhcp build options: 18 | @echo "CFLAGS = ${CFLAGS}" 19 | @echo "LDFLAGS = ${LDFLAGS}" 20 | @echo "CC = ${CC}" 21 | 22 | $(BIN): $(OBJ) 23 | @$(LD) -o $@ $(OBJ) $(LDFLAGS) 24 | 25 | $(OBJ): $(HDR) config.mk 26 | 27 | .c.o: 28 | @echo CC $< 29 | @$(CC) -c -o $@ $< $(CFLAGS) 30 | 31 | install: all 32 | @echo installing executables to $(DESTDIR)$(PREFIX)/sbin 33 | @mkdir -p $(DESTDIR)$(PREFIX)/sbin 34 | @cp -f $(BIN) $(DESTDIR)$(PREFIX)/sbin 35 | @cd $(DESTDIR)$(PREFIX)/sbin && chmod 755 $(BIN) 36 | @echo installing manual pages to $(DESTDIR)$(MANPREFIX)/man1 37 | @mkdir -p $(DESTDIR)$(MANPREFIX)/man1 38 | @for m in $(MAN); do sed "s/VERSION/$(VERSION)/g" < "$$m" > $(DESTDIR)$(MANPREFIX)/man1/"$$m"; done 39 | @cd $(DESTDIR)$(MANPREFIX)/man1 && chmod 644 $(MAN) 40 | 41 | uninstall: 42 | @echo removing executables from $(DESTDIR)$(PREFIX)/sbin 43 | @cd $(DESTDIR)$(PREFIX)/sbin && rm -f $(BIN) 44 | @echo removing manual pages from $(DESTDIR)$(MANPREFIX)/man1 45 | @cd $(DESTDIR)$(MANPREFIX)/man1 && rm -f $(MAN) 46 | 47 | clean: 48 | @echo cleaning 49 | @rm -f $(BIN) $(OBJ) util.a 50 | 51 | .PHONY: all options clean install uninstall 52 | -------------------------------------------------------------------------------- /compat.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define MIN(a,b) (((a)<(b))?(a):(b)) 5 | #define LEN(a) (sizeof(a) / sizeof((a)[0])) 6 | 7 | #if (defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN) \ 8 | || defined(__LITTLE_ENDIAN__) || defined(__LITTLEENDIAN__) 9 | #define PORT67 0x4300 10 | #define PORT68 0x4400 11 | #define MAGIC 0x63538263 12 | #else 13 | #if (defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN) \ 14 | || defined(__BIG_ENDIAN__) 15 | #define PORT67 67 16 | #define PORT68 68 17 | #define MAGIC 0x63825363 18 | #else 19 | #warning Could not work out endian 20 | #define PORT67 htons(67) 21 | #define PORT68 htons(68) 22 | #define MAGIC ntohl(0x63825363) 23 | #endif 24 | #endif 25 | 26 | extern int sock; 27 | extern const char *ifname; 28 | extern int wait_for_link; 29 | 30 | #define N_TIMERS 3 31 | extern int timers[]; 32 | extern struct in_addr server; 33 | extern struct in_addr client; 34 | 35 | void open_socket(const char *ifname); 36 | void close_socket(void); 37 | ssize_t udpsend(void *data, size_t n, int broadcast); 38 | ssize_t udprecv(void *data, size_t n); 39 | void get_hw_addr(const char *ifname, unsigned char *hwaddr); 40 | void create_timers(int recreate); 41 | void setip(struct in_addr ip, struct in_addr mask); 42 | void setgw(struct in_addr gw); 43 | 44 | #ifdef __linux__ 45 | 46 | #include 47 | 48 | #else 49 | 50 | int timerfd_gettime(int fd, struct itimerspec *curr_value); 51 | int timerfd_settime(int fd, int flags, 52 | const struct itimerspec *new_value, 53 | struct itimerspec *old_value); 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /sdhcp.1: -------------------------------------------------------------------------------- 1 | .Dd April 27, 2015 2 | .Dt SDHCP 1 3 | .Os 4 | .Sh NAME 5 | .Nm sdhcp 6 | .Nd a simple DHCP client 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl c Ar client ip 10 | .Op Fl d 11 | .Op Fl e Ar program 12 | .Op Fl f 13 | .Op Fl g 14 | .Op Fl i 15 | .Op Fl r Ar resolv.conf file 16 | .Op Ar interface 17 | .Op Ar client-id 18 | .Sh DESCRIPTION 19 | .Nm 20 | is a simple, tiny DHCP client. It runs until it enters the "Bound" 21 | state, then forks to the background and runs as a daemon to keep 22 | the lease alive. 23 | 24 | The 25 | .Pa client-id 26 | is a series of hex bytes. If you don't specify a 27 | .Pa client-id 28 | then it defaults to your hardware (MAC) address. The 29 | .Pa client-id 30 | is usually a DHCP Unqiue Identifier (DUID). 31 | If you are connecting to an internet provider and your DHCP address 32 | changes on every bind, you probably should set the 33 | .Pa client-id. 34 | 35 | .Sh OPTIONS 36 | .Bl -tag -width Ds 37 | .It Fl c Ar client IP 38 | try to request 39 | .Pa client IP 40 | and skip the initial discover. 41 | .It Fl d 42 | don't change DNS in 43 | .Pa /etc/resolv.conf . 44 | .It Fl e Ar program 45 | run 46 | .Ar program . 47 | Variables will be set, see VARIABLES. 48 | .It Fl f 49 | run in foreground. 50 | .It Fl i 51 | don't change interface information such as an IP address and GW. 52 | .It Fl g 53 | don't change GW 54 | .It Fl r Ar resolv.conf file 55 | alternate /etc/resolv.conf location 56 | .El 57 | .Sh VARIABLES 58 | The following variables are set: 59 | .Bl -tag -width Ds 60 | .It Ev CLIENT 61 | your client IP. 62 | .It Ev IFNAME 63 | interface name. 64 | .It Ev LEASE 65 | lease time. 66 | .It Ev MASK 67 | network mask. 68 | .It Ev ROUTER 69 | router (gateway) IP. 70 | .It Ev SERVER 71 | DHCP server IP. 72 | .It Ev SPID 73 | shdcp pid. 74 | .It Ev STATE 75 | BOUND or RENEW. 76 | .El 77 | 78 | The following variables are set if provided by the server: 79 | .Bl -tag -width Ds 80 | .It Ev DNS 81 | DNS IP. 82 | .It Ev DNS2 83 | alternate DNS IP. 84 | .It Ev DOMAIN 85 | domain name. 86 | .It Ev NTP 87 | NTP IP. 88 | .It Ev NTP2 89 | alternate NTP IP. 90 | .El 91 | .Sh BUGS 92 | I'm sure there are plenty. It only currently supports a small subset of 93 | DHCP options, and has been untested on larger networks. It ignores most of 94 | the DHCP options it understands. 95 | -------------------------------------------------------------------------------- /compat.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "compat.h" 16 | 17 | #ifdef __QNX__ 18 | #include 19 | #if _NTO_VERSION < 720 20 | // io-pkt does not support SIOCAIFADDR properly 21 | #undef SIOCAIFADDR 22 | #endif 23 | #endif 24 | 25 | static uint8_t hwaddr[ETHER_ADDR_LEN]; 26 | 27 | static void 28 | iptoaddr(struct sockaddr *ifaddr, struct in_addr ip, int port) 29 | { 30 | struct sockaddr_in *in = (struct sockaddr_in *)ifaddr; 31 | 32 | #ifndef __linux__ 33 | in->sin_len = sizeof(struct sockaddr_in); 34 | #endif 35 | in->sin_family = AF_INET; 36 | in->sin_port = port; 37 | in->sin_addr = ip; 38 | } 39 | 40 | void 41 | setip(struct in_addr ip, struct in_addr mask) 42 | { 43 | int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); 44 | if (fd == -1) 45 | err(1, "can't set ip, socket:"); 46 | 47 | struct ifreq ifreq = { 0 }; 48 | strcpy(ifreq.ifr_name, ifname); 49 | 50 | #ifdef SIOCAIFADDR 51 | struct ifaliasreq areq = { 0 }; 52 | strcpy(areq.ifra_name, ifname); 53 | 54 | static int first_time = 1; 55 | if (ioctl(fd, SIOCDIFADDR, &areq)) 56 | if (!first_time) 57 | warn("SIOCDIFADDR 0"); 58 | first_time = 0; 59 | 60 | iptoaddr(&areq.ifra_addr, ip, 0); 61 | iptoaddr(&areq.ifra_mask, mask, 0); 62 | if (ioctl(fd, SIOCAIFADDR, &areq)) 63 | warn("SIOCAIFADDR %s", inet_ntoa(ip)); 64 | #else 65 | // Linux only needs the sin_addr, but BSDish needs full sockaddr 66 | iptoaddr(&ifreq.ifr_addr, ip, 0); 67 | if (ioctl(fd, SIOCSIFADDR, &ifreq)) 68 | warn("SIOCSIFADDR"); 69 | if (mask.s_addr) { 70 | iptoaddr(&ifreq.ifr_addr, mask, 0); 71 | if (ioctl(fd, SIOCSIFNETMASK, &ifreq)) 72 | warn("SIOCSIFNETMASK"); 73 | } 74 | #endif 75 | ifreq.ifr_flags = IFF_UP; 76 | if (ioctl(fd, SIOCSIFFLAGS, &ifreq)) 77 | warn("SIOCSIFFLAGS"); 78 | 79 | close(fd); 80 | } 81 | 82 | #ifdef __linux__ 83 | 84 | void 85 | get_hw_addr(const char *ifname, unsigned char *hwaddr_in) 86 | { 87 | struct ifreq ifreq; 88 | 89 | memset(&ifreq, 0, sizeof(ifreq)); 90 | strcpy(ifreq.ifr_name, ifname); 91 | if (ioctl(sock, SIOCGIFHWADDR, &ifreq)) 92 | err(1, "SIOCGIFHWADDR"); 93 | 94 | memcpy(hwaddr, ifreq.ifr_hwaddr.sa_data, ETHER_ADDR_LEN); 95 | memcpy(hwaddr_in, ifreq.ifr_hwaddr.sa_data, ETHER_ADDR_LEN); 96 | } 97 | 98 | void 99 | create_timers(int recreate) 100 | { /* timerfd survives a fork, don't need to recreate */ 101 | if (recreate == 0) 102 | for (int i = 0; i < N_TIMERS; ++i) { 103 | timers[i] = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC); 104 | if (timers[i] == -1) 105 | err(1, "timerfd_create:"); 106 | } 107 | } 108 | 109 | #else 110 | 111 | #include 112 | #include 113 | 114 | void 115 | get_hw_addr(const char *ifname, unsigned char *hwaddr_out) 116 | { 117 | struct ifaddrs *ifa = NULL; 118 | struct sockaddr_dl *sa = NULL; 119 | 120 | if (getifaddrs(&ifa)) 121 | err(1, "getifaddrs"); 122 | 123 | for (struct ifaddrs *p = ifa; p; p = p->ifa_next) { 124 | if (p->ifa_addr->sa_family == AF_LINK && 125 | strcmp(p->ifa_name, ifname) == 0) { 126 | sa = (struct sockaddr_dl *)p->ifa_addr; 127 | if (sa->sdl_type == 1 || sa->sdl_type == 6) { // ethernet 128 | memcpy(hwaddr, LLADDR(sa), ETHER_ADDR_LEN); 129 | memcpy(hwaddr_out, hwaddr, ETHER_ADDR_LEN); 130 | freeifaddrs(ifa); 131 | return; 132 | } else 133 | errx(1, "INVALID %d", sa->sdl_type); 134 | } 135 | } 136 | 137 | errx(1, "No interface called '%s'", ifname); 138 | } 139 | 140 | #include 141 | #include 142 | 143 | static timer_t t_id[N_TIMERS]; 144 | static int t_wr_pipe[N_TIMERS]; 145 | 146 | static void 147 | sigalrm(int sig, siginfo_t *si, void *ctx) 148 | { 149 | (void)sig; (void)ctx; 150 | 151 | unsigned char n = si->si_value.sival_int; 152 | 153 | if (n < N_TIMERS) 154 | write(t_wr_pipe[n], &n, sizeof(n)); 155 | } 156 | 157 | void 158 | create_timers(int recreate) 159 | { 160 | struct sigaction act = { 161 | .sa_flags = SA_SIGINFO, 162 | .sa_sigaction = sigalrm, 163 | }; 164 | struct sigevent ev = { 165 | .sigev_notify = SIGEV_SIGNAL, 166 | .sigev_signo = SIGALRM, 167 | }; 168 | 169 | for (int id = 0; id < N_TIMERS; ++id) { 170 | ev.sigev_value.sival_int = id; 171 | 172 | if (timer_create(CLOCK_MONOTONIC, &ev, &t_id[id])) 173 | err(1, "timer_create"); 174 | } 175 | 176 | if (recreate) 177 | /* the pipes survive the fork() */ 178 | return; 179 | 180 | if (sigaction(SIGALRM, &act, NULL) < 0) 181 | err(1, "sigaction SIGALRM:"); 182 | 183 | for (int id = 0; id < N_TIMERS; ++id) { 184 | int pipes[2]; 185 | if (pipe(pipes)) 186 | err(1, "pipe"); 187 | 188 | timers[id] = pipes[0]; /* read end */ 189 | t_wr_pipe[id] = pipes[1]; /* write end */ 190 | } 191 | } 192 | 193 | int 194 | timerfd_gettime(int fd, struct itimerspec *curr_value) 195 | { 196 | for (int i = 0; i < N_TIMERS; ++i) 197 | if (timers[i] == fd) 198 | return timer_gettime(t_id[i], curr_value); 199 | 200 | errno = EBADF; 201 | return -1; 202 | } 203 | 204 | int 205 | timerfd_settime(int fd, int flags, 206 | const struct itimerspec *new_value, 207 | struct itimerspec *old_value) 208 | { 209 | for (int i = 0; i < N_TIMERS; ++i) 210 | if (timers[i] == fd) 211 | return timer_settime(t_id[i], flags, new_value, old_value); 212 | 213 | errno = EBADF; 214 | return -1; 215 | } 216 | 217 | #endif 218 | 219 | #ifdef __linux__ 220 | 221 | #include 222 | #include 223 | #include 224 | #include 225 | 226 | /* Fixed bootp header + 312 for optional */ 227 | #define BOOTP_SIZE (236 + 312) 228 | 229 | static struct pkt { 230 | struct ether_header ethhdr; 231 | struct ip iphdr; 232 | struct udphdr udphdr; 233 | uint32_t bootp[BOOTP_SIZE / sizeof(uint32_t)]; 234 | } __attribute__((packed)) pkt; 235 | 236 | /* pseudo header for udp calc */ 237 | static struct pseudohdr 238 | { 239 | unsigned long source_ip; 240 | unsigned long dest_ip; 241 | unsigned char reserved; 242 | unsigned char protocol; 243 | unsigned short udp_length; 244 | struct udphdr udphdr; 245 | unsigned char bootp[BOOTP_SIZE]; 246 | } __attribute__((packed)) pseudohdr; 247 | 248 | static struct in_addr dst_addr; 249 | static unsigned char dst_mac[ETHER_ADDR_LEN]; 250 | static unsigned int ifindex; 251 | 252 | /* RFC 1071. */ 253 | static uint16_t 254 | chksum16(const void *buf, int count) 255 | { 256 | int32_t sum = 0, shift; 257 | const uint16_t *p = buf; 258 | 259 | while (count > 1) { 260 | sum += *p++; 261 | count -= 2; 262 | } 263 | 264 | if (count > 0) 265 | sum += *p; 266 | 267 | /* Fold 32-bit sum to 16 bits */ 268 | if ((shift = sum >> 16)) 269 | sum = (sum & 0xffff) + shift; 270 | 271 | return ~sum; 272 | } 273 | 274 | /* open a socket */ 275 | void 276 | open_socket(const char *ifname) 277 | { 278 | int bcast = 1; 279 | 280 | if ((sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1) 281 | err(1, "socket:"); 282 | 283 | if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) == -1) 284 | err(1, "setsockopt broadcast:"); 285 | 286 | struct ifreq ifreq; 287 | memset(&ifreq, 0, sizeof(ifreq)); 288 | strcpy(ifreq.ifr_name, ifname); 289 | 290 | if (ioctl(sock, SIOCGIFINDEX, &ifreq)) 291 | err(1, "SIOCGIFINDEX"); 292 | ifindex = ifreq.ifr_ifindex; 293 | } 294 | 295 | void 296 | close_socket(void) 297 | { /* We close the socket for performance reasons */ 298 | if (sock != -1) { 299 | close(sock); 300 | sock = -1; 301 | } 302 | } 303 | 304 | ssize_t 305 | udpsend(void *data, size_t n, int broadcast) 306 | { 307 | if (sock == -1) 308 | open_socket(ifname); 309 | 310 | memset(&pkt, 0, sizeof(pkt)); 311 | 312 | if (broadcast) { 313 | memset(pkt.ethhdr.ether_dhost, 0xff, ETHER_ADDR_LEN); 314 | pkt.iphdr.ip_dst.s_addr = INADDR_BROADCAST; 315 | } else { 316 | memcpy(&pkt.ethhdr.ether_dhost, dst_mac, ETHER_ADDR_LEN); 317 | pkt.iphdr.ip_dst = dst_addr; 318 | pkt.iphdr.ip_src = client; 319 | } 320 | 321 | memcpy(pkt.ethhdr.ether_shost, hwaddr, ETHER_ADDR_LEN); 322 | pkt.ethhdr.ether_type = ntohs(ETHERTYPE_IP); 323 | 324 | pkt.iphdr.ip_v = 4; 325 | pkt.iphdr.ip_hl = 5; 326 | pkt.iphdr.ip_tos = IPTOS_LOWDELAY; 327 | pkt.iphdr.ip_len = htons(sizeof(struct ip) + sizeof(struct udphdr) + n); 328 | pkt.iphdr.ip_id = 0; 329 | pkt.iphdr.ip_off = htons(0x4000); /* DF set */ 330 | pkt.iphdr.ip_ttl = 16; 331 | pkt.iphdr.ip_p = IPPROTO_UDP; 332 | pkt.iphdr.ip_sum = chksum16(&pkt.iphdr, 20); 333 | 334 | pkt.udphdr.uh_sport = PORT68; 335 | pkt.udphdr.uh_dport = PORT67; 336 | pkt.udphdr.uh_ulen = htons(sizeof(struct udphdr) + n); 337 | 338 | memcpy(&pkt.bootp, data, n); 339 | 340 | memset(&pseudohdr, 0, sizeof(pseudohdr)); 341 | pseudohdr.source_ip = pkt.iphdr.ip_src.s_addr; 342 | pseudohdr.dest_ip = pkt.iphdr.ip_dst.s_addr; 343 | pseudohdr.protocol = pkt.iphdr.ip_p; 344 | pseudohdr.udp_length = htons(sizeof(struct udphdr) + n); 345 | 346 | memcpy(&pseudohdr.udphdr, &pkt.udphdr, sizeof(struct udphdr)); 347 | memcpy(&pseudohdr.bootp, data, n); 348 | int header_len = sizeof(pseudohdr) - BOOTP_SIZE + n; 349 | pkt.udphdr.uh_sum = chksum16(&pseudohdr, header_len); 350 | 351 | struct sockaddr_ll sa; 352 | memset(&sa, 0, sizeof (sa)); 353 | sa.sll_family = AF_PACKET; 354 | sa.sll_protocol = htons(ETH_P_IP); 355 | sa.sll_halen = ETHER_ADDR_LEN; 356 | memcpy(sa.sll_addr, hwaddr, ETHER_ADDR_LEN); 357 | sa.sll_ifindex = ifindex; 358 | 359 | size_t len = sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr) + n; 360 | ssize_t sent; 361 | while ((sent = sendto(sock, &pkt, len, 0, (struct sockaddr *)&sa, sizeof(sa))) == -1) 362 | if (errno != EINTR) 363 | err(1, "sendto:"); 364 | 365 | return sent; 366 | } 367 | 368 | ssize_t 369 | udprecv(void *data, size_t n) 370 | { 371 | struct pkt recv; 372 | int r; 373 | 374 | memset(&recv, 0, sizeof(recv)); 375 | while ((r = read(sock, &recv, sizeof(recv))) == -1) 376 | if (errno != EINTR) 377 | err(1, "read"); 378 | 379 | if (ntohs(recv.ethhdr.ether_type) != ETHERTYPE_IP) 380 | return -1; // not an IP packet 381 | 382 | if (recv.udphdr.uh_sport != PORT67 || recv.udphdr.uh_dport != PORT68) 383 | return -1; /* not a dhcp packet */ 384 | 385 | r -= sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr); 386 | if (r < 236) 387 | return -1; /* too small to be a dhcp packet */ 388 | if (r > (int)n) 389 | r = n; 390 | 391 | if (memcmp(recv.bootp + 7, hwaddr, ETHER_ADDR_LEN)) 392 | return -1; /* not our mac */ 393 | 394 | dst_addr = recv.iphdr.ip_src; 395 | memcpy(dst_mac, &recv.ethhdr.ether_shost, ETHER_ADDR_LEN); 396 | memcpy(data, &recv.bootp, r); 397 | 398 | return r; 399 | } 400 | 401 | #else 402 | #include 403 | 404 | #define ACTIVE_FLAGS (IFM_AVALID | IFM_ACTIVE) 405 | #define IS_ACTIVE(s) (((s) & ACTIVE_FLAGS) == ACTIVE_FLAGS) 406 | 407 | static struct in_addr dst_addr; 408 | static struct in_addr ip_zero; 409 | 410 | void open_socket(const char *ifname) 411 | { 412 | int set = 1; 413 | 414 | sock = socket(AF_INET, SOCK_DGRAM, 0); 415 | if (sock == -1) 416 | err(1, "socket"); 417 | 418 | if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &set, sizeof(set)) == -1) 419 | err(1, "SO_BROADCAST:"); 420 | 421 | #ifdef SO_BINDTODEVICE 422 | struct ifreq ifreq = { 0 }; 423 | strcpy(ifreq.ifr_name, ifname); 424 | if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq)) == -1) 425 | err(1, "SO_BINDTODEVICE:"); 426 | #endif 427 | 428 | struct sockaddr addr; 429 | iptoaddr(&addr, ip_zero, PORT68); 430 | if (bind(sock, (void*)&addr, sizeof(addr)) != 0) 431 | err(1, "bind:"); 432 | 433 | if (wait_for_link) { 434 | while (1) { 435 | // Must reset every time 436 | struct ifmediareq ifmr = { 0 }; 437 | strlcpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name)); 438 | if (ioctl(sock, SIOCGIFMEDIA, &ifmr)) 439 | err(1, "MEDIA"); 440 | if (IS_ACTIVE(ifmr.ifm_status)) { 441 | break; 442 | } 443 | usleep(999999); 444 | } 445 | } 446 | } 447 | 448 | void close_socket(void) {} 449 | 450 | /* sendto UDP wrapper */ 451 | ssize_t 452 | udpsend(void *data, size_t n, int broadcast) 453 | { 454 | struct sockaddr addr; 455 | ssize_t sent; 456 | struct in_addr ip; 457 | int flags = 0; 458 | 459 | if (broadcast) { 460 | ip.s_addr = INADDR_BROADCAST; 461 | flags |= MSG_DONTROUTE; 462 | } else 463 | ip = dst_addr; 464 | 465 | iptoaddr(&addr, ip, PORT67); /* bootp server */ 466 | while ((sent = sendto(sock, data, n, flags, &addr, sizeof(addr))) == -1) 467 | if (errno != EINTR) { 468 | warn("sendto"); 469 | break; 470 | } 471 | 472 | return sent; 473 | } 474 | 475 | /* recvfrom UDP wrapper */ 476 | ssize_t 477 | udprecv(void *data, size_t n) 478 | { 479 | struct sockaddr addr; 480 | socklen_t len = sizeof(addr); 481 | ssize_t r; 482 | 483 | while ((r = recvfrom(sock, data, n, 0, &addr, &len)) == -1) 484 | if (errno != EINTR) 485 | err(1, "recvfrom:"); 486 | 487 | unsigned *bp = data; 488 | if (memcmp(bp + 7, hwaddr, ETHER_ADDR_LEN)) 489 | return -1; /* not our mac */ 490 | 491 | dst_addr = ((struct sockaddr_in *)&addr)->sin_addr; 492 | 493 | return r; 494 | } 495 | 496 | #endif 497 | 498 | #ifdef __linux__ 499 | void 500 | setgw(struct in_addr gw) 501 | { 502 | int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); 503 | if (fd == -1) 504 | err(1, "can't set gw, socket:"); 505 | 506 | struct rtentry rtreq = { 507 | .rt_flags = RTF_UP | RTF_GATEWAY, 508 | .rt_dst.sa_family = AF_INET, 509 | .rt_gateway.sa_family = AF_INET, 510 | .rt_genmask.sa_family = AF_INET, 511 | }; 512 | ((struct sockaddr_in *)&rtreq.rt_gateway)->sin_addr = gw; 513 | if (ioctl(fd, SIOCADDRT, &rtreq)) 514 | warn("SIOCADDRT"); 515 | 516 | close(fd); 517 | } 518 | #else 519 | #define RTM_ADDRS ((1 << RTAX_DST) | (1 << RTAX_GATEWAY) | (1 << RTAX_NETMASK)) 520 | #define RTM_SEQ 42 521 | #define RTM_FLAGS (RTF_STATIC | RTF_UP | RTF_GATEWAY) 522 | 523 | static int 524 | rtmsg_send(int s, int cmd, struct in_addr gw) 525 | { 526 | struct rtmsg { 527 | struct rt_msghdr hdr; 528 | struct sockaddr data[3]; 529 | } rtmsg; 530 | 531 | memset(&rtmsg, 0, sizeof(rtmsg)); 532 | rtmsg.hdr.rtm_type = cmd; 533 | rtmsg.hdr.rtm_flags = RTM_FLAGS; 534 | rtmsg.hdr.rtm_version = RTM_VERSION; 535 | rtmsg.hdr.rtm_seq = RTM_SEQ; 536 | rtmsg.hdr.rtm_addrs = RTM_ADDRS; 537 | 538 | struct sockaddr_in sa; 539 | memset(&sa, 0, sizeof(sa)); 540 | sa.sin_len = sizeof(sa); 541 | sa.sin_family = AF_INET; 542 | 543 | iptoaddr(&rtmsg.data[0], ip_zero, 0); // DST 544 | iptoaddr(&rtmsg.data[1], gw, 0); // GATEWAY 545 | iptoaddr(&rtmsg.data[2], ip_zero, 0); // NETMASK 546 | 547 | rtmsg.hdr.rtm_msglen = sizeof(rtmsg); 548 | if (write(s, &rtmsg, rtmsg.hdr.rtm_msglen) < 0) 549 | return -1; 550 | 551 | return 0; 552 | } 553 | 554 | void 555 | setgw(struct in_addr gw) 556 | { 557 | int s = socket(PF_ROUTE, SOCK_RAW, 0); 558 | if (s < 0) 559 | err(1, "can't set gw, socket:"); 560 | 561 | shutdown(s, SHUT_RD); /* Don't want to read back our messages */ 562 | 563 | if (rtmsg_send(s, RTM_ADD, gw) == 0) { 564 | close(s); 565 | return; 566 | } 567 | 568 | if (errno == EEXIST) 569 | if (rtmsg_send(s, RTM_CHANGE, gw) == 0) { 570 | close(s); 571 | return; 572 | } 573 | 574 | close(s); 575 | err(1, "rtmsg send:"); 576 | } 577 | #endif 578 | -------------------------------------------------------------------------------- /sdhcp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "compat.h" 23 | 24 | /* The xid is redundant on ethernet and wireless networks since we 25 | * have a MAC. Since the xid is client only, just hardcode it. 26 | */ 27 | #define XID 0x21433412 28 | 29 | #define BROADCAST (1 << 7) 30 | 31 | struct bootp { 32 | uint8_t op; 33 | uint8_t htype; 34 | uint8_t hlen; 35 | uint8_t hops; // unused 36 | uint32_t xid; 37 | uint16_t secs; // unused 38 | uint16_t flags; 39 | struct in_addr ciaddr; 40 | struct in_addr yiaddr; 41 | uint32_t siaddr; // unused 42 | uint32_t giaddr; // unused 43 | uint64_t chaddr; 44 | uint64_t chaddr2; // unused 45 | uint8_t sname[64]; // unused 46 | uint8_t file[128]; // unused 47 | // optdata 48 | // we unroll as much as we can 49 | uint32_t magic; 50 | uint8_t type_id; 51 | uint8_t type_len; 52 | uint8_t type_data; 53 | uint8_t cid_id; 54 | uint8_t cid_len; 55 | uint8_t optdata[312 - 9]; 56 | } __attribute((packed)); 57 | 58 | _Static_assert(sizeof(struct bootp) == 548, "bootp size"); 59 | 60 | enum { 61 | DHCPdiscover = 1, 62 | DHCPoffer, 63 | DHCPrequest, 64 | DHCPdecline, 65 | DHCPack, 66 | DHCPnak, 67 | DHCPrelease, 68 | DHCPinform, 69 | 70 | Timeout0 = 200, 71 | Timeout1, 72 | Timeout2, 73 | 74 | OBpad = 0, 75 | OBmask = 1, 76 | OBrouter = 3, 77 | OBnameserver = 5, 78 | OBdnsserver = 6, 79 | OBhostname = 12, 80 | OBdomainname = 15, 81 | OBbaddr = 28, 82 | OBntp = 42, 83 | ODipaddr = 50, /* 0x32 */ 84 | ODlease = 51, 85 | ODoverload = 52, 86 | ODtype = 53, /* 0x35 */ 87 | ODserverid = 54, /* 0x36 */ 88 | ODparams = 55, /* 0x37 */ 89 | ODmessage = 56, 90 | ODmaxmsg = 57, 91 | ODrenewaltime = 58, 92 | ODrebindingtime = 59, 93 | ODvendorclass = 60, 94 | ODclientid = 61, /* 0x3d */ 95 | ODtftpserver = 66, 96 | ODbootfile = 67, 97 | OBend = 255, 98 | }; 99 | 100 | static struct bootp bp; 101 | 102 | static const unsigned char params[] = { 103 | OBmask, OBrouter, OBdnsserver, OBdomainname, OBntp, 104 | ODlease, ODrenewaltime, ODrebindingtime 105 | }; 106 | 107 | /* One socket to rule them all */ 108 | int sock = -1; 109 | 110 | int wait_for_link = 1; 111 | 112 | /* conf */ 113 | static uint64_t hwaddr64; 114 | static char hostname[_POSIX_HOST_NAME_MAX + 1]; 115 | static int hostname_len; 116 | const char *ifname = "eth0"; 117 | static char *resolvconf = "/etc/resolv.conf"; 118 | static unsigned char cid[24]; 119 | static int cid_len; 120 | static char *program; 121 | int timers[N_TIMERS]; 122 | /* sav */ 123 | struct in_addr client; 124 | struct in_addr server; 125 | static struct in_addr mask; 126 | static struct in_addr router; 127 | static struct in_addr dns[2]; 128 | static struct in_addr ntp[2]; 129 | static char domainname[64]; 130 | static uint32_t renewaltime, rebindingtime, leasetime; 131 | 132 | static int dflag = 1; /* change DNS in /etc/resolv.conf ? */ 133 | static int iflag = 1; /* set IP ? */ 134 | static int gflag = 1; /* set GW ? */ 135 | static int fflag; /* run in foreground */ 136 | static int always_broadcast; 137 | 138 | static void 139 | cat(int dfd, char *src) 140 | { 141 | char buf[BUFSIZ]; 142 | int n; 143 | 144 | int fd = open(src, O_RDONLY); 145 | if (fd == -1) 146 | return; /* can't read, but don't error out */ 147 | while ((n = read(fd, buf, sizeof(buf))) > 0) 148 | write(dfd, buf, n); 149 | close(fd); 150 | } 151 | 152 | static void 153 | setdns(struct in_addr *dns) 154 | { 155 | char buf[128]; 156 | 157 | if (dflag == 0) 158 | return; 159 | 160 | int fd = creat(resolvconf, 0644); 161 | if (fd == -1) { 162 | warn("can't change %s", resolvconf); 163 | return; 164 | } 165 | cat(fd, "/etc/resolv.conf.head"); 166 | int n = snprintf(buf, sizeof(buf), "\nnameserver %s\n", inet_ntoa(dns[0])); 167 | if (dns[1].s_addr) 168 | n += snprintf(buf + n, sizeof(buf) - n, "nameserver %s\n", inet_ntoa(dns[1])); 169 | if (*domainname) 170 | n += snprintf(buf + n, sizeof(buf) - n, "search %s\n", domainname); 171 | write(fd, buf, n); 172 | cat(fd, "/etc/resolv.conf.tail"); 173 | close(fd); 174 | } 175 | 176 | static void 177 | optget(struct bootp *bp, void *data, int opt, int n) 178 | { 179 | unsigned char *p = &bp->type_id; 180 | unsigned char *top = ((unsigned char *)bp) + sizeof(*bp); 181 | int code, len; 182 | 183 | while (p < top) { 184 | code = *p++; 185 | if (code == OBpad) 186 | continue; 187 | if (code == OBend || p == top) 188 | break; 189 | len = *p++; 190 | if (len > top - p) 191 | break; 192 | if (code == opt) { 193 | memcpy(data, p, MIN(len, n)); 194 | break; 195 | } 196 | p += len; 197 | } 198 | } 199 | 200 | static unsigned char * 201 | optput(unsigned char *p, int opt, const void *data, size_t len) 202 | { 203 | *p++ = opt; 204 | *p++ = (unsigned char)len; 205 | memcpy(p, data, len); 206 | 207 | return p + len; 208 | } 209 | 210 | static void 211 | dhcpsend(int type, uint16_t broadcast) 212 | { 213 | struct bootp bootp = { 214 | .op = 1, // boot request 215 | .htype = 1, // ethernet 216 | .hlen = ETHER_ADDR_LEN, 217 | .xid = XID, 218 | .flags = broadcast, 219 | .chaddr = hwaddr64, 220 | .magic = MAGIC, 221 | .type_id = ODtype, 222 | .type_len = 1, 223 | .type_data = type, 224 | .cid_id = ODclientid, 225 | .cid_len = cid_len, 226 | }; 227 | 228 | memcpy(bootp.optdata, cid, cid_len); 229 | uint8_t *p = bootp.optdata + cid_len; 230 | p = optput(p, OBhostname, (unsigned char *)hostname, hostname_len); 231 | 232 | switch (type) { 233 | case DHCPdiscover: 234 | break; 235 | case DHCPrequest: 236 | p = optput(p, ODipaddr, &client, sizeof(client)); 237 | p = optput(p, ODserverid, &server, sizeof(server)); 238 | p = optput(p, ODparams, params, sizeof(params)); 239 | break; 240 | case DHCPrelease: 241 | bootp.ciaddr = client; 242 | p = optput(p, ODipaddr, &client, sizeof(client)); 243 | p = optput(p, ODserverid, &server, sizeof(server)); 244 | break; 245 | } 246 | *p++ = OBend; 247 | 248 | if (always_broadcast) 249 | broadcast = BROADCAST; 250 | udpsend(&bootp, p - (uint8_t *)&bootp, broadcast); 251 | } 252 | 253 | static int 254 | dhcprecv(void) 255 | { 256 | unsigned char type; 257 | struct pollfd pfd[] = { 258 | { .fd = sock, .events = POLLIN }, 259 | { .fd = timers[0], .events = POLLIN }, 260 | { .fd = timers[1], .events = POLLIN }, 261 | { .fd = timers[2], .events = POLLIN }, 262 | }; 263 | uint64_t n; 264 | 265 | again: 266 | while (poll(pfd, LEN(pfd), -1) == -1) 267 | if (errno != EINTR) 268 | err(1, "poll:"); 269 | if (pfd[0].revents) { 270 | memset(&bp, 0, sizeof(bp)); 271 | if (udprecv(&bp, sizeof(bp)) == -1) 272 | /* Not our packet */ 273 | goto again; 274 | optget(&bp, &type, ODtype, sizeof(type)); 275 | return type; 276 | } 277 | if (pfd[1].revents) { 278 | type = Timeout0; 279 | read(timers[0], &n, sizeof(n)); 280 | } 281 | if (pfd[2].revents) { 282 | type = Timeout1; 283 | read(timers[1], &n, sizeof(n)); 284 | } 285 | if (pfd[3].revents) { 286 | type = Timeout2; 287 | read(timers[2], &n, sizeof(n)); 288 | } 289 | return type; 290 | } 291 | 292 | static void 293 | callout(const char *state) 294 | { 295 | char buf[32]; 296 | 297 | if (!program) 298 | return; 299 | 300 | setenv("STATE", state, 1); 301 | setenv("IFNAME", ifname, 1); 302 | snprintf(buf, sizeof(buf), "%d", getpid()); 303 | setenv("SPID", buf, 1); 304 | snprintf(buf, sizeof(buf), "%u", leasetime); 305 | setenv("LEASE", buf, 1); 306 | setenv("SERVER", inet_ntoa(server), 1); 307 | setenv("CLIENT", inet_ntoa(client), 1); 308 | setenv("MASK", inet_ntoa(mask), 1); 309 | setenv("ROUTER", inet_ntoa(router), 1); 310 | if (dns[0].s_addr) 311 | setenv("DNS", inet_ntoa(dns[0]), 1); 312 | if (dns[1].s_addr) 313 | setenv("DNS2", inet_ntoa(dns[1]), 1); 314 | if (*domainname) 315 | setenv("DOMAIN", domainname, 1); 316 | if (ntp[0].s_addr) 317 | setenv("NTP", inet_ntoa(ntp[0]), 1); 318 | if (ntp[1].s_addr) 319 | setenv("NTP2", inet_ntoa(ntp[1]), 1); 320 | system(program); 321 | } 322 | 323 | static void 324 | settimeout(int n, uint32_t seconds) 325 | { 326 | const struct itimerspec ts = { .it_value.tv_sec = seconds }; 327 | if (timerfd_settime(timers[n], 0, &ts, NULL) < 0) 328 | err(1, "timerfd_settime:"); 329 | } 330 | 331 | /* sets timer t to expire halfway to the expiration of timer n, minimum of 60 seconds */ 332 | static void 333 | calctimeout(int n, int t) 334 | { 335 | struct itimerspec ts; 336 | 337 | if (timerfd_gettime(timers[n], &ts) < 0) 338 | err(1, "timerfd_gettime:"); 339 | ts.it_value.tv_nsec /= 2; 340 | if (ts.it_value.tv_sec % 2) 341 | ts.it_value.tv_nsec += 500000000; 342 | ts.it_value.tv_sec /= 2; 343 | if (ts.it_value.tv_sec < 60) { 344 | ts.it_value.tv_sec = 60; 345 | ts.it_value.tv_nsec = 0; 346 | } 347 | if (timerfd_settime(timers[t], 0, &ts, NULL) < 0) 348 | err(1, "timerfd_settime:"); 349 | } 350 | 351 | static void 352 | parse_reply(void) 353 | { 354 | optget(&bp, &mask, OBmask, sizeof(mask)); 355 | optget(&bp, &router, OBrouter, sizeof(router)); 356 | optget(&bp, &dns, OBdnsserver, sizeof(dns)); 357 | optget(&bp, &ntp, OBntp, sizeof(ntp)); 358 | optget(&bp, domainname, OBdomainname, sizeof(domainname)); 359 | optget(&bp, &leasetime, ODlease, sizeof(leasetime)); 360 | leasetime = ntohl(leasetime); 361 | 362 | /* Renew and rebind times are optional. It is faster to just 363 | * calculate the times. Assumes: lease > 4s and < ~20 years. 364 | */ 365 | renewaltime = leasetime / 2; 366 | rebindingtime = leasetime * 7 / 8; 367 | } 368 | 369 | static void 370 | run(int fast_start) 371 | { 372 | int forked = 0; 373 | uint32_t t; 374 | 375 | if (fast_start) 376 | goto Requesting; 377 | 378 | Init: 379 | client.s_addr = 0; 380 | server.s_addr = 0; 381 | dhcpsend(DHCPdiscover, BROADCAST); 382 | settimeout(0, 1); 383 | goto Selecting; 384 | Selecting: 385 | for (;;) { 386 | switch (dhcprecv()) { 387 | case DHCPoffer: 388 | client = bp.yiaddr; 389 | optget(&bp, &server, ODserverid, sizeof(server)); 390 | goto Requesting; 391 | case Timeout0: 392 | goto Init; 393 | } 394 | } 395 | Requesting: 396 | for (t = 4; t <= 64; t *= 2) { 397 | dhcpsend(DHCPrequest, BROADCAST); 398 | settimeout(0, t); 399 | for (;;) { 400 | switch (dhcprecv()) { 401 | case DHCPack: 402 | goto Bound; 403 | case DHCPnak: 404 | goto Init; 405 | case Timeout0: 406 | break; 407 | default: 408 | continue; 409 | } 410 | break; 411 | } 412 | } 413 | /* no response from DHCPREQUEST after several attempts, go to INIT */ 414 | goto Init; 415 | Bound: 416 | close_socket(); /* currently raw sockets only */ 417 | 418 | parse_reply(); 419 | if (iflag) { 420 | setip(client, mask); 421 | if (gflag) 422 | setgw(router); 423 | } 424 | setdns(dns); 425 | 426 | if (!forked) 427 | write(1, "Congrats! You should be on the 'net.\n", 37); 428 | if (!fflag && !forked) { 429 | if (fork()) 430 | exit(0); 431 | create_timers(1); 432 | } 433 | forked = 1; /* doesn't hurt to always set this */ 434 | 435 | /* call after fork() to get pid */ 436 | callout("BOUND"); 437 | 438 | Renewed: 439 | settimeout(0, renewaltime); 440 | settimeout(1, rebindingtime); 441 | settimeout(2, leasetime); 442 | for (;;) { 443 | switch (dhcprecv()) { 444 | case Timeout0: /* t1 elapsed */ 445 | goto Renewing; 446 | case Timeout1: /* t2 elapsed */ 447 | goto Rebinding; 448 | case Timeout2: /* lease expired */ 449 | goto Init; 450 | } 451 | } 452 | Renewing: 453 | dhcpsend(DHCPrequest, 0); 454 | calctimeout(1, 0); 455 | for (;;) { 456 | switch (dhcprecv()) { 457 | case DHCPack: 458 | parse_reply(); 459 | callout("RENEW"); 460 | close_socket(); /* currently raw sockets only */ 461 | goto Renewed; 462 | case Timeout0: /* resend request */ 463 | goto Renewing; 464 | case Timeout1: /* t2 elapsed */ 465 | goto Rebinding; 466 | case Timeout2: 467 | case DHCPnak: 468 | goto Init; 469 | } 470 | } 471 | Rebinding: 472 | calctimeout(2, 0); 473 | dhcpsend(DHCPrequest, BROADCAST); 474 | for (;;) { 475 | switch (dhcprecv()) { 476 | case DHCPack: 477 | goto Bound; 478 | case Timeout0: /* resend request */ 479 | goto Rebinding; 480 | case Timeout2: /* lease expired */ 481 | case DHCPnak: 482 | goto Init; 483 | } 484 | } 485 | } 486 | 487 | static void 488 | cleanexit(int unused) 489 | { 490 | (void)unused; 491 | dhcpsend(DHCPrelease, 0); 492 | _exit(0); 493 | } 494 | 495 | static void __attribute__((noreturn)) 496 | usage(int rc) 497 | { 498 | errx(rc, " [-c client_ip] [-d] [-e program] [-f] [-i] [-r resolv.conf] [-W]\n" 499 | "\t[ifname] [clientid]"); 500 | } 501 | 502 | static uint8_t 503 | fromhex(char nibble) 504 | { 505 | if (nibble >= '0' && nibble <= '9') 506 | return nibble - '0'; 507 | else if (nibble >= 'a' && nibble <= 'f') 508 | return nibble - 'a' + 10; 509 | else if (nibble >= 'A' && nibble <= 'F') 510 | return nibble - 'A' + 10; 511 | else 512 | errx(1, "Bad nibble %c\n", nibble); 513 | return 0; // unreachable 514 | } 515 | 516 | static int 517 | str2bytes(const char *str, uint8_t *bytes, int len) 518 | { 519 | int slen = strlen(str); 520 | if ((slen & 1) || slen > (len * 2)) 521 | printf("invalid CID"); 522 | 523 | while (*str) { 524 | *bytes = (fromhex(*str++) << 4); 525 | *bytes++ |= fromhex(*str++); 526 | } 527 | 528 | return slen / 2; 529 | } 530 | 531 | int 532 | main(int argc, char *argv[]) 533 | { 534 | int c, fast_start = 0; 535 | 536 | while ((c = getopt(argc, argv, "c:de:fghir:BW")) != EOF) 537 | switch (c) { 538 | case 'c': // client IP 539 | if (inet_aton(optarg, &client) == 0) 540 | errx(1, "Invalid client address '%s'", optarg); 541 | fast_start = 1; 542 | break; 543 | case 'd': /* don't update DNS in /etc/resolv.conf */ 544 | dflag = 0; 545 | break; 546 | case 'e': /* run program */ 547 | program = optarg; 548 | break; 549 | case 'f': /* run in foreground */ 550 | fflag = 1; 551 | break; 552 | case 'g': /* don't set gw */ 553 | gflag = 0; 554 | break; 555 | case 'h': 556 | usage(0); 557 | case 'i': /* don't set ip */ 558 | iflag = 0; 559 | break; 560 | case 'r': /* resolv.conf filename */ 561 | resolvconf = optarg; 562 | break; 563 | case 'B': 564 | always_broadcast = 1; 565 | break; 566 | case 'W': 567 | wait_for_link = 0; 568 | break; 569 | default: 570 | usage(1); 571 | } 572 | 573 | if (optind < argc) { 574 | ifname = argv[optind++]; /* interface name */ 575 | /* If we verify here, we can use strcpy() rather than the more 576 | * expensive, and less portable, strlcpy(). 577 | */ 578 | if (strlen(ifname) >= IF_NAMESIZE) { 579 | fprintf(stderr, "Interface %s too big\n", ifname); 580 | exit(1); 581 | } 582 | } 583 | if (optind < argc) { /* client-id */ 584 | char *id = argv[optind]; 585 | if (*id == '0' && *(id + 1) == 'x') 586 | id += 2; // backwards compatibility 587 | cid_len = str2bytes(id, cid, sizeof(cid)); 588 | } 589 | 590 | signal(SIGTERM, cleanexit); 591 | 592 | if (gethostname(hostname, sizeof(hostname)) == -1) 593 | err(1, "gethostname:"); 594 | hostname_len = strlen(hostname); 595 | 596 | /* Set interface up. 597 | * For BSD we seem to need to set ip to 0.0.0.0. 598 | */ 599 | if (iflag) { 600 | struct in_addr zero = { 0 }; 601 | setip(zero, zero); 602 | } 603 | 604 | open_socket(ifname); 605 | 606 | unsigned char hwaddr[ETHER_ADDR_LEN]; 607 | get_hw_addr(ifname, hwaddr); 608 | memcpy(&hwaddr64, hwaddr, sizeof(hwaddr)); 609 | 610 | if (cid_len == 0) { 611 | cid[0] = 1; 612 | memcpy(cid + 1, hwaddr, ETHER_ADDR_LEN); 613 | cid_len = ETHER_ADDR_LEN + 1; 614 | } 615 | 616 | create_timers(0); 617 | 618 | run(fast_start); 619 | 620 | return 0; 621 | } 622 | --------------------------------------------------------------------------------