diff options
Diffstat (limited to 'networking/udhcpc.c')
-rw-r--r-- | networking/udhcpc.c | 1587 |
1 files changed, 0 insertions, 1587 deletions
diff --git a/networking/udhcpc.c b/networking/udhcpc.c deleted file mode 100644 index c05ca4557..000000000 --- a/networking/udhcpc.c +++ /dev/null | |||
@@ -1,1587 +0,0 @@ | |||
1 | /* dhcpd.c | ||
2 | * | ||
3 | * udhcp DHCP client | ||
4 | * | ||
5 | * Russ Dill <Russ.Dill@asu.edu> July 2001 | ||
6 | * | ||
7 | * Converted to busybox by Glenn McGrath August 2002 | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License as published by | ||
11 | * the Free Software Foundation; either version 2 of the License, or | ||
12 | * (at your option) any later version. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, | ||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with this program; if not, write to the Free Software | ||
21 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
22 | */ | ||
23 | |||
24 | #include <stdio.h> | ||
25 | #include <sys/time.h> | ||
26 | #include <sys/types.h> | ||
27 | #include <sys/file.h> | ||
28 | #include <unistd.h> | ||
29 | #include <getopt.h> | ||
30 | #include <stdlib.h> | ||
31 | #include <sys/socket.h> | ||
32 | #include <netinet/in.h> | ||
33 | #include <arpa/inet.h> | ||
34 | #include <signal.h> | ||
35 | #include <time.h> | ||
36 | #include <string.h> | ||
37 | #include <sys/ioctl.h> | ||
38 | #include <net/if.h> | ||
39 | #include <errno.h> | ||
40 | #include <netinet/ip.h> | ||
41 | #include <netinet/udp.h> | ||
42 | #include <sys/types.h> | ||
43 | #include <sys/wait.h> | ||
44 | |||
45 | #if __GLIBC__ >=2 && __GLIBC_MINOR >= 1 | ||
46 | #include <netpacket/packet.h> | ||
47 | #include <net/ethernet.h> | ||
48 | #else | ||
49 | #include <asm/types.h> | ||
50 | #include <linux/if_packet.h> | ||
51 | #include <linux/if_ether.h> | ||
52 | #endif | ||
53 | #include "libbb.h" | ||
54 | |||
55 | static int state; | ||
56 | static unsigned long requested_ip; /* = 0 */ | ||
57 | static unsigned long server_addr; | ||
58 | static unsigned long timeout; | ||
59 | static int packet_num; /* = 0 */ | ||
60 | static int fd_main; | ||
61 | |||
62 | /* #define DEBUG */ | ||
63 | |||
64 | #define VERSION "0.9.7" | ||
65 | |||
66 | #define LISTEN_NONE 0 | ||
67 | #define LISTEN_KERNEL 1 | ||
68 | #define LISTEN_RAW 2 | ||
69 | static int listen_mode; | ||
70 | |||
71 | #define DEFAULT_SCRIPT "/usr/share/udhcpc/default.script" | ||
72 | |||
73 | #define DHCP_END 0xFF | ||
74 | #define TYPE_MASK 0x0F | ||
75 | #define BROADCAST_FLAG 0x8000 | ||
76 | |||
77 | #define SERVER_PORT 67 | ||
78 | |||
79 | #define DHCP_MAGIC 0x63825363 | ||
80 | |||
81 | #define BOOTREQUEST 1 | ||
82 | #define BOOTREPLY 2 | ||
83 | |||
84 | #define ETH_10MB 1 | ||
85 | #define ETH_10MB_LEN 6 | ||
86 | |||
87 | #define OPTION_FIELD 0 | ||
88 | #define FILE_FIELD 1 | ||
89 | #define SNAME_FIELD 2 | ||
90 | |||
91 | #define INIT_SELECTING 0 | ||
92 | #define REQUESTING 1 | ||
93 | #define BOUND 2 | ||
94 | #define RENEWING 3 | ||
95 | #define REBINDING 4 | ||
96 | #define INIT_REBOOT 5 | ||
97 | #define RENEW_REQUESTED 6 | ||
98 | #define RELEASED 7 | ||
99 | |||
100 | #define CLIENT_PORT 68 | ||
101 | |||
102 | #define DHCPDISCOVER 1 | ||
103 | #define DHCPOFFER 2 | ||
104 | #define DHCPREQUEST 3 | ||
105 | #define DHCPDECLINE 4 | ||
106 | #define DHCPACK 5 | ||
107 | #define DHCPNAK 6 | ||
108 | #define DHCPRELEASE 7 | ||
109 | #define DHCPINFORM 8 | ||
110 | |||
111 | /* DHCP option codes (partial list) */ | ||
112 | #define DHCP_PADDING 0x00 | ||
113 | #define DHCP_SUBNET 0x01 | ||
114 | #define DHCP_TIME_OFFSET 0x02 | ||
115 | #define DHCP_ROUTER 0x03 | ||
116 | #define DHCP_TIME_SERVER 0x04 | ||
117 | #define DHCP_NAME_SERVER 0x05 | ||
118 | #define DHCP_DNS_SERVER 0x06 | ||
119 | #define DHCP_LOG_SERVER 0x07 | ||
120 | #define DHCP_COOKIE_SERVER 0x08 | ||
121 | #define DHCP_LPR_SERVER 0x09 | ||
122 | #define DHCP_HOST_NAME 0x0c | ||
123 | #define DHCP_BOOT_SIZE 0x0d | ||
124 | #define DHCP_DOMAIN_NAME 0x0f | ||
125 | #define DHCP_SWAP_SERVER 0x10 | ||
126 | #define DHCP_ROOT_PATH 0x11 | ||
127 | #define DHCP_IP_TTL 0x17 | ||
128 | #define DHCP_MTU 0x1a | ||
129 | #define DHCP_BROADCAST 0x1c | ||
130 | #define DHCP_NTP_SERVER 0x2a | ||
131 | #define DHCP_WINS_SERVER 0x2c | ||
132 | #define DHCP_REQUESTED_IP 0x32 | ||
133 | #define DHCP_LEASE_TIME 0x33 | ||
134 | #define DHCP_OPTION_OVER 0x34 | ||
135 | #define DHCP_MESSAGE_TYPE 0x35 | ||
136 | #define DHCP_SERVER_ID 0x36 | ||
137 | #define DHCP_PARAM_REQ 0x37 | ||
138 | #define DHCP_MESSAGE 0x38 | ||
139 | #define DHCP_MAX_SIZE 0x39 | ||
140 | #define DHCP_T1 0x3a | ||
141 | #define DHCP_T2 0x3b | ||
142 | #define DHCP_VENDOR 0x3c | ||
143 | #define DHCP_CLIENT_ID 0x3d | ||
144 | |||
145 | /* miscellaneous defines */ | ||
146 | #define MAC_BCAST_ADDR (unsigned char *) "\xff\xff\xff\xff\xff\xff" | ||
147 | #define OPT_CODE 0 | ||
148 | #define OPT_LEN 1 | ||
149 | #define OPT_DATA 2 | ||
150 | |||
151 | enum { | ||
152 | OPTION_IP = 1, | ||
153 | OPTION_IP_PAIR, | ||
154 | OPTION_STRING, | ||
155 | OPTION_BOOLEAN, | ||
156 | OPTION_U8, | ||
157 | OPTION_U16, | ||
158 | OPTION_S16, | ||
159 | OPTION_U32, | ||
160 | OPTION_S32 | ||
161 | }; | ||
162 | |||
163 | #define OPTION_REQ 0x10 /* have the client request this option */ | ||
164 | #define OPTION_LIST 0x20 /* There can be a list of 1 or more of these */ | ||
165 | |||
166 | #ifdef SYSLOG | ||
167 | # define LOG(level, str, args...) do { printf(str, ## args); \ | ||
168 | printf("\n"); \ | ||
169 | syslog(level, str, ## args); } while(0) | ||
170 | # define OPEN_LOG(name) openlog(name, 0, 0) | ||
171 | # define CLOSE_LOG() closelog() | ||
172 | #else | ||
173 | # define LOG_EMERG "EMERGENCY!" | ||
174 | # define LOG_ALERT "ALERT!" | ||
175 | # define LOG_CRIT "critical!" | ||
176 | # define LOG_WARNING "warning" | ||
177 | # define LOG_ERR "error" | ||
178 | # define LOG_INFO "info" | ||
179 | # define LOG_DEBUG "debug" | ||
180 | # define LOG(level, str, args...) do { printf("%s, " str "\n", level, ## args); } while(0) | ||
181 | # define OPEN_LOG(name) | ||
182 | # define CLOSE_LOG() | ||
183 | #endif | ||
184 | |||
185 | #ifdef DEBUG | ||
186 | # undef DEBUG | ||
187 | # define DEBUG(level, str, args...) LOG(level, str, ## args) | ||
188 | # define DEBUGGING | ||
189 | #else | ||
190 | # define DEBUG(level, str, args...) | ||
191 | #endif | ||
192 | |||
193 | struct dhcpMessage { | ||
194 | u_int8_t op; | ||
195 | u_int8_t htype; | ||
196 | u_int8_t hlen; | ||
197 | u_int8_t hops; | ||
198 | u_int32_t xid; | ||
199 | u_int16_t secs; | ||
200 | u_int16_t flags; | ||
201 | u_int32_t ciaddr; | ||
202 | u_int32_t yiaddr; | ||
203 | u_int32_t siaddr; | ||
204 | u_int32_t giaddr; | ||
205 | u_int8_t chaddr[16]; | ||
206 | u_int8_t sname[64]; | ||
207 | u_int8_t file[128]; | ||
208 | u_int32_t cookie; | ||
209 | u_int8_t options[308]; /* 312 - cookie */ | ||
210 | }; | ||
211 | |||
212 | struct client_config_t { | ||
213 | char foreground; /* Do not fork */ | ||
214 | char quit_after_lease; /* Quit after obtaining lease */ | ||
215 | char abort_if_no_lease; /* Abort if no lease */ | ||
216 | char *interface; /* The name of the interface to use */ | ||
217 | char *pidfile; /* Optionally store the process ID */ | ||
218 | char *script; /* User script to run at dhcp events */ | ||
219 | unsigned char *clientid; /* Optional client id to use */ | ||
220 | unsigned char *hostname; /* Optional hostname to use */ | ||
221 | int ifindex; /* Index number of the interface to use */ | ||
222 | unsigned char arp[6]; /* Our arp address */ | ||
223 | }; | ||
224 | |||
225 | struct client_config_t client_config = { | ||
226 | /* Default options. */ | ||
227 | abort_if_no_lease:0, | ||
228 | foreground:0, | ||
229 | quit_after_lease:0, | ||
230 | interface:"eth0", | ||
231 | pidfile:NULL, | ||
232 | script:DEFAULT_SCRIPT, | ||
233 | clientid:NULL, | ||
234 | hostname:NULL, | ||
235 | ifindex:0, | ||
236 | arp:"\0\0\0\0\0\0", /* appease gcc-3.0 */ | ||
237 | }; | ||
238 | |||
239 | struct dhcp_option { | ||
240 | char name[10]; | ||
241 | char flags; | ||
242 | unsigned char code; | ||
243 | }; | ||
244 | |||
245 | struct udp_dhcp_packet { | ||
246 | struct iphdr ip; | ||
247 | struct udphdr udp; | ||
248 | struct dhcpMessage data; | ||
249 | }; | ||
250 | |||
251 | static const struct dhcp_option options[] = { | ||
252 | /* name[10] flags code */ | ||
253 | {"subnet", OPTION_IP | OPTION_REQ, 0x01}, | ||
254 | {"timezone", OPTION_S32, 0x02}, | ||
255 | {"router", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x03}, | ||
256 | {"timesvr", OPTION_IP | OPTION_LIST, 0x04}, | ||
257 | {"namesvr", OPTION_IP | OPTION_LIST, 0x05}, | ||
258 | {"dns", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x06}, | ||
259 | {"logsvr", OPTION_IP | OPTION_LIST, 0x07}, | ||
260 | {"cookiesvr", OPTION_IP | OPTION_LIST, 0x08}, | ||
261 | {"lprsvr", OPTION_IP | OPTION_LIST, 0x09}, | ||
262 | {"hostname", OPTION_STRING | OPTION_REQ, 0x0c}, | ||
263 | {"bootsize", OPTION_U16, 0x0d}, | ||
264 | {"domain", OPTION_STRING | OPTION_REQ, 0x0f}, | ||
265 | {"swapsvr", OPTION_IP, 0x10}, | ||
266 | {"rootpath", OPTION_STRING, 0x11}, | ||
267 | {"ipttl", OPTION_U8, 0x17}, | ||
268 | {"mtu", OPTION_U16, 0x1a}, | ||
269 | {"broadcast", OPTION_IP | OPTION_REQ, 0x1c}, | ||
270 | {"ntpsrv", OPTION_IP | OPTION_LIST, 0x2a}, | ||
271 | {"wins", OPTION_IP | OPTION_LIST, 0x2c}, | ||
272 | {"requestip", OPTION_IP, 0x32}, | ||
273 | {"lease", OPTION_U32, 0x33}, | ||
274 | {"dhcptype", OPTION_U8, 0x35}, | ||
275 | {"serverid", OPTION_IP, 0x36}, | ||
276 | {"message", OPTION_STRING, 0x38}, | ||
277 | {"tftp", OPTION_STRING, 0x42}, | ||
278 | {"bootfile", OPTION_STRING, 0x43}, | ||
279 | {"", 0x00, 0x00} | ||
280 | }; | ||
281 | |||
282 | /* Lengths of the different option types */ | ||
283 | static const unsigned char option_lengths[] = { | ||
284 | [OPTION_IP] = 4, | ||
285 | [OPTION_IP_PAIR] = 8, | ||
286 | [OPTION_BOOLEAN] = 1, | ||
287 | [OPTION_STRING] = 1, | ||
288 | [OPTION_U8] = 1, | ||
289 | [OPTION_U16] = 2, | ||
290 | [OPTION_S16] = 2, | ||
291 | [OPTION_U32] = 4, | ||
292 | [OPTION_S32] = 4 | ||
293 | }; | ||
294 | |||
295 | /* get a rough idea of how long an option will be (rounding up...) */ | ||
296 | static const unsigned char max_option_length[] = { | ||
297 | [OPTION_IP] = sizeof("255.255.255.255 "), | ||
298 | [OPTION_IP_PAIR] = sizeof("255.255.255.255 ") * 2, | ||
299 | [OPTION_STRING] = 1, | ||
300 | [OPTION_BOOLEAN] = sizeof("yes "), | ||
301 | [OPTION_U8] = sizeof("255 "), | ||
302 | [OPTION_U16] = sizeof("65535 "), | ||
303 | [OPTION_S16] = sizeof("-32768 "), | ||
304 | [OPTION_U32] = sizeof("4294967295 "), | ||
305 | [OPTION_S32] = sizeof("-2147483684 "), | ||
306 | }; | ||
307 | |||
308 | /* return the position of the 'end' option (no bounds checking) */ | ||
309 | static int end_option(unsigned char *optionptr) | ||
310 | { | ||
311 | int i = 0; | ||
312 | |||
313 | while (optionptr[i] != DHCP_END) { | ||
314 | if (optionptr[i] == DHCP_PADDING) | ||
315 | i++; | ||
316 | else | ||
317 | i += optionptr[i + OPT_LEN] + 2; | ||
318 | } | ||
319 | return i; | ||
320 | } | ||
321 | |||
322 | /* add an option string to the options (an option string contains an option code, | ||
323 | * length, then data) */ | ||
324 | static int add_option_string(unsigned char *optionptr, unsigned char *string) | ||
325 | { | ||
326 | int end = end_option(optionptr); | ||
327 | |||
328 | /* end position + string length + option code/length + end option */ | ||
329 | if (end + string[OPT_LEN] + 2 + 1 >= 308) { | ||
330 | LOG(LOG_ERR, "Option 0x%02x did not fit into the packet!", | ||
331 | string[OPT_CODE]); | ||
332 | return 0; | ||
333 | } | ||
334 | DEBUG(LOG_INFO, "adding option 0x%02x", string[OPT_CODE]); | ||
335 | memcpy(optionptr + end, string, string[OPT_LEN] + 2); | ||
336 | optionptr[end + string[OPT_LEN] + 2] = DHCP_END; | ||
337 | return string[OPT_LEN] + 2; | ||
338 | } | ||
339 | |||
340 | /* add a one to four byte option to a packet */ | ||
341 | static int add_simple_option(unsigned char *optionptr, unsigned char code, | ||
342 | u_int32_t data) | ||
343 | { | ||
344 | char length = 0; | ||
345 | int i; | ||
346 | unsigned char option[2 + 4]; | ||
347 | unsigned char *u8; | ||
348 | u_int16_t *u16; | ||
349 | u_int32_t *u32; | ||
350 | u_int32_t aligned; | ||
351 | |||
352 | u8 = (unsigned char *) &aligned; | ||
353 | u16 = (u_int16_t *) & aligned; | ||
354 | u32 = &aligned; | ||
355 | |||
356 | for (i = 0; options[i].code; i++) | ||
357 | if (options[i].code == code) { | ||
358 | length = option_lengths[options[i].flags & TYPE_MASK]; | ||
359 | } | ||
360 | |||
361 | if (!length) { | ||
362 | DEBUG(LOG_ERR, "Could not add option 0x%02x", code); | ||
363 | return 0; | ||
364 | } | ||
365 | |||
366 | option[OPT_CODE] = code; | ||
367 | option[OPT_LEN] = length; | ||
368 | |||
369 | switch (length) { | ||
370 | case 1: | ||
371 | *u8 = data; | ||
372 | break; | ||
373 | case 2: | ||
374 | *u16 = data; | ||
375 | break; | ||
376 | case 4: | ||
377 | *u32 = data; | ||
378 | break; | ||
379 | } | ||
380 | |||
381 | memcpy(option + 2, &aligned, length); | ||
382 | return add_option_string(optionptr, option); | ||
383 | } | ||
384 | |||
385 | static u_int16_t checksum(void *addr, int count) | ||
386 | { | ||
387 | /* Compute Internet Checksum for "count" bytes | ||
388 | * beginning at location "addr". | ||
389 | */ | ||
390 | register int32_t sum = 0; | ||
391 | u_int16_t *source = (u_int16_t *) addr; | ||
392 | |||
393 | while (count > 1) { | ||
394 | /* This is the inner loop */ | ||
395 | sum += *source++; | ||
396 | count -= 2; | ||
397 | } | ||
398 | |||
399 | /* Add left-over byte, if any */ | ||
400 | if (count > 0) { | ||
401 | sum += *(unsigned char *) source; | ||
402 | } | ||
403 | |||
404 | /* Fold 32-bit sum to 16 bits */ | ||
405 | while (sum >> 16) { | ||
406 | sum = (sum & 0xffff) + (sum >> 16); | ||
407 | } | ||
408 | |||
409 | return ~sum; | ||
410 | } | ||
411 | |||
412 | /* Constuct a ip/udp header for a packet, and specify the source and dest hardware address */ | ||
413 | static int raw_packet(struct dhcpMessage *payload, u_int32_t source_ip, | ||
414 | int source_port, u_int32_t dest_ip, int dest_port, | ||
415 | unsigned char *dest_arp, int ifindex) | ||
416 | { | ||
417 | int l_fd; | ||
418 | int result; | ||
419 | struct sockaddr_ll dest; | ||
420 | struct udp_dhcp_packet packet; | ||
421 | |||
422 | if ((l_fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { | ||
423 | DEBUG(LOG_ERR, "socket call failed: %s", strerror(errno)); | ||
424 | return -1; | ||
425 | } | ||
426 | |||
427 | memset(&dest, 0, sizeof(dest)); | ||
428 | memset(&packet, 0, sizeof(packet)); | ||
429 | |||
430 | dest.sll_family = AF_PACKET; | ||
431 | dest.sll_protocol = htons(ETH_P_IP); | ||
432 | dest.sll_ifindex = ifindex; | ||
433 | dest.sll_halen = 6; | ||
434 | memcpy(dest.sll_addr, dest_arp, 6); | ||
435 | if (bind(l_fd, (struct sockaddr *) &dest, sizeof(struct sockaddr_ll)) < 0) { | ||
436 | DEBUG(LOG_ERR, "bind call failed: %s", strerror(errno)); | ||
437 | close(l_fd); | ||
438 | return -1; | ||
439 | } | ||
440 | |||
441 | packet.ip.protocol = IPPROTO_UDP; | ||
442 | packet.ip.saddr = source_ip; | ||
443 | packet.ip.daddr = dest_ip; | ||
444 | packet.udp.source = htons(source_port); | ||
445 | packet.udp.dest = htons(dest_port); | ||
446 | packet.udp.len = htons(sizeof(packet.udp) + sizeof(struct dhcpMessage)); /* cheat on the psuedo-header */ | ||
447 | packet.ip.tot_len = packet.udp.len; | ||
448 | memcpy(&(packet.data), payload, sizeof(struct dhcpMessage)); | ||
449 | packet.udp.check = checksum(&packet, sizeof(struct udp_dhcp_packet)); | ||
450 | |||
451 | packet.ip.tot_len = htons(sizeof(struct udp_dhcp_packet)); | ||
452 | packet.ip.ihl = sizeof(packet.ip) >> 2; | ||
453 | packet.ip.version = IPVERSION; | ||
454 | packet.ip.ttl = IPDEFTTL; | ||
455 | packet.ip.check = checksum(&(packet.ip), sizeof(packet.ip)); | ||
456 | |||
457 | result = | ||
458 | sendto(l_fd, &packet, sizeof(struct udp_dhcp_packet), 0, | ||
459 | (struct sockaddr *) &dest, sizeof(dest)); | ||
460 | if (result <= 0) { | ||
461 | DEBUG(LOG_ERR, "write on socket failed: %s", strerror(errno)); | ||
462 | } | ||
463 | close(l_fd); | ||
464 | return result; | ||
465 | } | ||
466 | |||
467 | /* Let the kernel do all the work for packet generation */ | ||
468 | static int kernel_packet(struct dhcpMessage *payload, u_int32_t source_ip, | ||
469 | int source_port, u_int32_t dest_ip, int dest_port) | ||
470 | { | ||
471 | int n = 1; | ||
472 | int l_fd, result; | ||
473 | struct sockaddr_in client; | ||
474 | |||
475 | if ((l_fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { | ||
476 | return -1; | ||
477 | } | ||
478 | |||
479 | if (setsockopt(l_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &n, sizeof(n)) == | ||
480 | -1) { | ||
481 | return -1; | ||
482 | } | ||
483 | |||
484 | memset(&client, 0, sizeof(client)); | ||
485 | client.sin_family = AF_INET; | ||
486 | client.sin_port = htons(source_port); | ||
487 | client.sin_addr.s_addr = source_ip; | ||
488 | |||
489 | if (bind(l_fd, (struct sockaddr *) &client, sizeof(struct sockaddr)) == | ||
490 | -1) { | ||
491 | return -1; | ||
492 | } | ||
493 | |||
494 | memset(&client, 0, sizeof(client)); | ||
495 | client.sin_family = AF_INET; | ||
496 | client.sin_port = htons(dest_port); | ||
497 | client.sin_addr.s_addr = dest_ip; | ||
498 | |||
499 | if (connect(l_fd, (struct sockaddr *) &client, sizeof(struct sockaddr)) == | ||
500 | -1) { | ||
501 | return -1; | ||
502 | } | ||
503 | |||
504 | result = write(l_fd, payload, sizeof(struct dhcpMessage)); | ||
505 | close(l_fd); | ||
506 | return result; | ||
507 | } | ||
508 | |||
509 | /* initialize a packet with the proper defaults */ | ||
510 | static void init_packet(struct dhcpMessage *packet, char type) | ||
511 | { | ||
512 | struct vendor { | ||
513 | char vendor, length; | ||
514 | char str[sizeof("udhcp " VERSION)]; | ||
515 | } | ||
516 | vendor_id = { | ||
517 | DHCP_VENDOR, sizeof("udhcp " VERSION) - 1, "udhcp " VERSION}; | ||
518 | |||
519 | memset(packet, 0, sizeof(struct dhcpMessage)); | ||
520 | switch (type) { | ||
521 | case DHCPDISCOVER: | ||
522 | case DHCPREQUEST: | ||
523 | case DHCPRELEASE: | ||
524 | case DHCPINFORM: | ||
525 | packet->op = BOOTREQUEST; | ||
526 | break; | ||
527 | case DHCPOFFER: | ||
528 | case DHCPACK: | ||
529 | case DHCPNAK: | ||
530 | packet->op = BOOTREPLY; | ||
531 | } | ||
532 | packet->htype = ETH_10MB; | ||
533 | packet->hlen = ETH_10MB_LEN; | ||
534 | packet->cookie = htonl(DHCP_MAGIC); | ||
535 | packet->options[0] = DHCP_END; | ||
536 | add_simple_option(packet->options, DHCP_MESSAGE_TYPE, type); | ||
537 | |||
538 | memcpy(packet->chaddr, client_config.arp, 6); | ||
539 | add_option_string(packet->options, client_config.clientid); | ||
540 | if (client_config.hostname) { | ||
541 | add_option_string(packet->options, client_config.hostname); | ||
542 | } | ||
543 | add_option_string(packet->options, (unsigned char *) &vendor_id); | ||
544 | } | ||
545 | |||
546 | |||
547 | /* Add a paramater request list for stubborn DHCP servers. Pull the data | ||
548 | * from the struct in options.c. Don't do bounds checking here because it | ||
549 | * goes towards the head of the packet. */ | ||
550 | static void add_requests(struct dhcpMessage *packet) | ||
551 | { | ||
552 | int end = end_option(packet->options); | ||
553 | int i, len = 0; | ||
554 | |||
555 | packet->options[end + OPT_CODE] = DHCP_PARAM_REQ; | ||
556 | for (i = 0; options[i].code; i++) { | ||
557 | if (options[i].flags & OPTION_REQ) { | ||
558 | packet->options[end + OPT_DATA + len++] = options[i].code; | ||
559 | } | ||
560 | } | ||
561 | packet->options[end + OPT_LEN] = len; | ||
562 | packet->options[end + OPT_DATA + len] = DHCP_END; | ||
563 | |||
564 | } | ||
565 | |||
566 | /* Broadcast a DHCP discover packet to the network, with an optionally requested IP */ | ||
567 | static inline int send_discover(unsigned long xid, unsigned long requested) | ||
568 | { | ||
569 | struct dhcpMessage packet; | ||
570 | |||
571 | init_packet(&packet, DHCPDISCOVER); | ||
572 | packet.xid = xid; | ||
573 | if (requested) { | ||
574 | add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); | ||
575 | } | ||
576 | add_requests(&packet); | ||
577 | DEBUG(LOG_DEBUG, "Sending discover..."); | ||
578 | return raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, | ||
579 | SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); | ||
580 | } | ||
581 | |||
582 | /* Broadcasts a DHCP request message */ | ||
583 | static inline int send_selecting(unsigned long xid, unsigned long server, | ||
584 | unsigned long requested) | ||
585 | { | ||
586 | struct dhcpMessage packet; | ||
587 | struct in_addr addr; | ||
588 | |||
589 | init_packet(&packet, DHCPREQUEST); | ||
590 | packet.xid = xid; | ||
591 | |||
592 | add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); | ||
593 | add_simple_option(packet.options, DHCP_SERVER_ID, server); | ||
594 | |||
595 | add_requests(&packet); | ||
596 | addr.s_addr = requested; | ||
597 | DEBUG(LOG_DEBUG, "Sending select for %s...", inet_ntoa(addr)); | ||
598 | return raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, | ||
599 | SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); | ||
600 | } | ||
601 | |||
602 | |||
603 | /* Unicasts or broadcasts a DHCP renew message */ | ||
604 | static int send_renew(unsigned long xid, unsigned long server, | ||
605 | unsigned long ciaddr) | ||
606 | { | ||
607 | struct dhcpMessage packet; | ||
608 | |||
609 | init_packet(&packet, DHCPREQUEST); | ||
610 | packet.xid = xid; | ||
611 | packet.ciaddr = ciaddr; | ||
612 | |||
613 | add_requests(&packet); | ||
614 | DEBUG(LOG_DEBUG, "Sending renew..."); | ||
615 | if (server) { | ||
616 | return kernel_packet(&packet, ciaddr, CLIENT_PORT, server, | ||
617 | SERVER_PORT); | ||
618 | } | ||
619 | return raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, | ||
620 | SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); | ||
621 | } | ||
622 | |||
623 | /* Create a random xid */ | ||
624 | static unsigned long random_xid(void) | ||
625 | { | ||
626 | static int initialized; | ||
627 | |||
628 | if (!initialized) { | ||
629 | srand(time(0)); | ||
630 | initialized++; | ||
631 | } | ||
632 | return rand(); | ||
633 | } | ||
634 | |||
635 | /* just a little helper */ | ||
636 | static void change_mode(int new_mode) | ||
637 | { | ||
638 | DEBUG(LOG_INFO, "entering %s listen mode", | ||
639 | new_mode ? (new_mode == 1 ? "kernel" : "raw") : "none"); | ||
640 | close(fd_main); | ||
641 | fd_main = -1; | ||
642 | listen_mode = new_mode; | ||
643 | } | ||
644 | |||
645 | |||
646 | /* SIGUSR1 handler (renew) */ | ||
647 | static void renew_requested(int sig) | ||
648 | { | ||
649 | sig = 0; | ||
650 | LOG(LOG_INFO, "Received SIGUSR1"); | ||
651 | if (state == BOUND || state == RENEWING || state == REBINDING || | ||
652 | state == RELEASED) { | ||
653 | change_mode(LISTEN_KERNEL); | ||
654 | packet_num = 0; | ||
655 | state = RENEW_REQUESTED; | ||
656 | } | ||
657 | |||
658 | if (state == RELEASED) { | ||
659 | change_mode(LISTEN_RAW); | ||
660 | state = INIT_SELECTING; | ||
661 | } | ||
662 | |||
663 | /* Kill any timeouts because the user wants this to hurry along */ | ||
664 | timeout = 0; | ||
665 | } | ||
666 | |||
667 | /* get an option with bounds checking (warning, not aligned). */ | ||
668 | static unsigned char *get_option(struct dhcpMessage *packet, int code) | ||
669 | { | ||
670 | int i, length; | ||
671 | unsigned char *optionptr; | ||
672 | int over = 0, done = 0, curr = OPTION_FIELD; | ||
673 | |||
674 | optionptr = packet->options; | ||
675 | i = 0; | ||
676 | length = 308; | ||
677 | while (!done) { | ||
678 | if (i >= length) { | ||
679 | LOG(LOG_WARNING, "bogus packet, option fields too long."); | ||
680 | return NULL; | ||
681 | } | ||
682 | if (optionptr[i + OPT_CODE] == code) { | ||
683 | if (i + 1 + optionptr[i + OPT_LEN] >= length) { | ||
684 | LOG(LOG_WARNING, "bogus packet, option fields too long."); | ||
685 | return NULL; | ||
686 | } | ||
687 | return optionptr + i + 2; | ||
688 | } | ||
689 | switch (optionptr[i + OPT_CODE]) { | ||
690 | case DHCP_PADDING: | ||
691 | i++; | ||
692 | break; | ||
693 | case DHCP_OPTION_OVER: | ||
694 | if (i + 1 + optionptr[i + OPT_LEN] >= length) { | ||
695 | LOG(LOG_WARNING, "bogus packet, option fields too long."); | ||
696 | return NULL; | ||
697 | } | ||
698 | over = optionptr[i + 3]; | ||
699 | i += optionptr[OPT_LEN] + 2; | ||
700 | break; | ||
701 | case DHCP_END: | ||
702 | if (curr == OPTION_FIELD && over & FILE_FIELD) { | ||
703 | optionptr = packet->file; | ||
704 | i = 0; | ||
705 | length = 128; | ||
706 | curr = FILE_FIELD; | ||
707 | } else if (curr == FILE_FIELD && over & SNAME_FIELD) { | ||
708 | optionptr = packet->sname; | ||
709 | i = 0; | ||
710 | length = 64; | ||
711 | curr = SNAME_FIELD; | ||
712 | } else { | ||
713 | done = 1; | ||
714 | } | ||
715 | break; | ||
716 | default: | ||
717 | i += optionptr[OPT_LEN + i] + 2; | ||
718 | } | ||
719 | } | ||
720 | return NULL; | ||
721 | } | ||
722 | |||
723 | static int sprintip(char *dest, char *pre, unsigned char *ip) | ||
724 | { | ||
725 | return sprintf(dest, "%s%d.%d.%d.%d ", pre, ip[0], ip[1], ip[2], ip[3]); | ||
726 | } | ||
727 | |||
728 | |||
729 | /* Fill dest with the text of option 'option'. */ | ||
730 | static inline void fill_options(char *dest, unsigned char *option, | ||
731 | const struct dhcp_option *type_p) | ||
732 | { | ||
733 | int type, optlen; | ||
734 | u_int16_t val_u16; | ||
735 | int16_t val_s16; | ||
736 | u_int32_t val_u32; | ||
737 | int32_t val_s32; | ||
738 | int len = option[OPT_LEN - 2]; | ||
739 | |||
740 | dest += sprintf(dest, "%s=", type_p->name); | ||
741 | |||
742 | type = type_p->flags & TYPE_MASK; | ||
743 | optlen = option_lengths[type]; | ||
744 | for (;;) { | ||
745 | switch (type) { | ||
746 | case OPTION_IP_PAIR: | ||
747 | dest += sprintip(dest, "", option); | ||
748 | *(dest++) = '/'; | ||
749 | option += 4; | ||
750 | optlen = 4; | ||
751 | case OPTION_IP: /* Works regardless of host byte order. */ | ||
752 | dest += sprintip(dest, "", option); | ||
753 | break; | ||
754 | case OPTION_BOOLEAN: | ||
755 | dest += sprintf(dest, *option ? "yes " : "no "); | ||
756 | break; | ||
757 | case OPTION_U8: | ||
758 | dest += sprintf(dest, "%u ", *option); | ||
759 | break; | ||
760 | case OPTION_U16: | ||
761 | memcpy(&val_u16, option, 2); | ||
762 | dest += sprintf(dest, "%u ", ntohs(val_u16)); | ||
763 | break; | ||
764 | case OPTION_S16: | ||
765 | memcpy(&val_s16, option, 2); | ||
766 | dest += sprintf(dest, "%d ", ntohs(val_s16)); | ||
767 | break; | ||
768 | case OPTION_U32: | ||
769 | memcpy(&val_u32, option, 4); | ||
770 | dest += sprintf(dest, "%lu ", (unsigned long) ntohl(val_u32)); | ||
771 | break; | ||
772 | case OPTION_S32: | ||
773 | memcpy(&val_s32, option, 4); | ||
774 | dest += sprintf(dest, "%ld ", (long) ntohl(val_s32)); | ||
775 | break; | ||
776 | case OPTION_STRING: | ||
777 | memcpy(dest, option, len); | ||
778 | dest[len] = '\0'; | ||
779 | return; /* Short circuit this case */ | ||
780 | } | ||
781 | option += optlen; | ||
782 | len -= optlen; | ||
783 | if (len <= 0) { | ||
784 | break; | ||
785 | } | ||
786 | } | ||
787 | } | ||
788 | |||
789 | static char *find_env(const char *prefix, char *defaultstr) | ||
790 | { | ||
791 | extern char **environ; | ||
792 | char **ptr; | ||
793 | const int len = strlen(prefix); | ||
794 | |||
795 | for (ptr = environ; *ptr != NULL; ptr++) { | ||
796 | if (strncmp(prefix, *ptr, len) == 0) { | ||
797 | return *ptr; | ||
798 | } | ||
799 | } | ||
800 | return defaultstr; | ||
801 | } | ||
802 | |||
803 | /* put all the paramaters into an environment */ | ||
804 | static char **fill_envp(struct dhcpMessage *packet) | ||
805 | { | ||
806 | /* supported options are easily added here */ | ||
807 | int num_options = 0; | ||
808 | int i, j; | ||
809 | char **envp; | ||
810 | unsigned char *temp; | ||
811 | char over = 0; | ||
812 | |||
813 | if (packet == NULL) { | ||
814 | num_options = 0; | ||
815 | } else { | ||
816 | for (i = 0; options[i].code; i++) { | ||
817 | if (get_option(packet, options[i].code)) { | ||
818 | num_options++; | ||
819 | } | ||
820 | } | ||
821 | if (packet->siaddr) { | ||
822 | num_options++; | ||
823 | } | ||
824 | if ((temp = get_option(packet, DHCP_OPTION_OVER))) { | ||
825 | over = *temp; | ||
826 | } | ||
827 | if (!(over & FILE_FIELD) && packet->file[0]) { | ||
828 | num_options++; | ||
829 | } | ||
830 | if (!(over & SNAME_FIELD) && packet->sname[0]) { | ||
831 | num_options++; | ||
832 | } | ||
833 | } | ||
834 | |||
835 | envp = xmalloc((num_options + 5) * sizeof(char *)); | ||
836 | envp[0] = xmalloc(sizeof("interface=") + strlen(client_config.interface)); | ||
837 | sprintf(envp[0], "interface=%s", client_config.interface); | ||
838 | envp[1] = find_env("PATH", "PATH=/bin:/usr/bin:/sbin:/usr/sbin"); | ||
839 | envp[2] = find_env("HOME", "HOME=/"); | ||
840 | |||
841 | if (packet == NULL) { | ||
842 | envp[3] = NULL; | ||
843 | return envp; | ||
844 | } | ||
845 | |||
846 | envp[3] = xmalloc(sizeof("ip=255.255.255.255")); | ||
847 | sprintip(envp[3], "ip=", (unsigned char *) &packet->yiaddr); | ||
848 | for (i = 0, j = 4; options[i].code; i++) { | ||
849 | if ((temp = get_option(packet, options[i].code))) { | ||
850 | envp[j] = | ||
851 | xmalloc(max_option_length[(&options[i])->flags & TYPE_MASK] * | ||
852 | (temp[OPT_LEN - 2] / | ||
853 | option_lengths[(&options[i])->flags & TYPE_MASK]) + | ||
854 | strlen((&options[i])->name) + 2); | ||
855 | fill_options(envp[j], temp, &options[i]); | ||
856 | j++; | ||
857 | } | ||
858 | } | ||
859 | if (packet->siaddr) { | ||
860 | envp[j] = xmalloc(sizeof("siaddr=255.255.255.255")); | ||
861 | sprintip(envp[j++], "siaddr=", (unsigned char *) &packet->yiaddr); | ||
862 | } | ||
863 | if (!(over & FILE_FIELD) && packet->file[0]) { | ||
864 | /* watch out for invalid packets */ | ||
865 | packet->file[sizeof(packet->file) - 1] = '\0'; | ||
866 | envp[j] = xmalloc(sizeof("boot_file=") + strlen(packet->file)); | ||
867 | sprintf(envp[j++], "boot_file=%s", packet->file); | ||
868 | } | ||
869 | if (!(over & SNAME_FIELD) && packet->sname[0]) { | ||
870 | /* watch out for invalid packets */ | ||
871 | packet->sname[sizeof(packet->sname) - 1] = '\0'; | ||
872 | envp[j] = xmalloc(sizeof("sname=") + strlen(packet->sname)); | ||
873 | sprintf(envp[j++], "sname=%s", packet->sname); | ||
874 | } | ||
875 | envp[j] = NULL; | ||
876 | return envp; | ||
877 | } | ||
878 | |||
879 | /* Call a script with a par file and env vars */ | ||
880 | static void run_script(struct dhcpMessage *packet, const char *name) | ||
881 | { | ||
882 | int pid; | ||
883 | char **envp; | ||
884 | |||
885 | if (client_config.script == NULL) { | ||
886 | return; | ||
887 | } | ||
888 | |||
889 | /* call script */ | ||
890 | pid = fork(); | ||
891 | if (pid) { | ||
892 | waitpid(pid, NULL, 0); | ||
893 | return; | ||
894 | } else if (pid == 0) { | ||
895 | envp = fill_envp(packet); | ||
896 | |||
897 | /* close fd's? */ | ||
898 | |||
899 | /* exec script */ | ||
900 | DEBUG(LOG_INFO, "execle'ing %s", client_config.script); | ||
901 | execle(client_config.script, client_config.script, name, NULL, envp); | ||
902 | LOG(LOG_ERR, "script %s failed: %s", | ||
903 | client_config.script, strerror(errno)); | ||
904 | exit(1); | ||
905 | } | ||
906 | } | ||
907 | |||
908 | /* SIGUSR2 handler (release) */ | ||
909 | static void release_requested(int sig) | ||
910 | { | ||
911 | sig = 0; | ||
912 | LOG(LOG_INFO, "Received SIGUSR2"); | ||
913 | /* send release packet */ | ||
914 | if (state == BOUND || state == RENEWING || state == REBINDING) { | ||
915 | struct dhcpMessage packet; | ||
916 | |||
917 | init_packet(&packet, DHCPRELEASE); | ||
918 | packet.xid = random_xid(); | ||
919 | packet.ciaddr = requested_ip; | ||
920 | |||
921 | add_simple_option(packet.options, DHCP_REQUESTED_IP, requested_ip); | ||
922 | add_simple_option(packet.options, DHCP_SERVER_ID, server_addr); | ||
923 | |||
924 | DEBUG(LOG_DEBUG, "Sending release..."); | ||
925 | kernel_packet(&packet, requested_ip, CLIENT_PORT, server_addr, | ||
926 | SERVER_PORT); | ||
927 | run_script(NULL, "deconfig"); | ||
928 | } | ||
929 | |||
930 | change_mode(LISTEN_NONE); | ||
931 | state = RELEASED; | ||
932 | timeout = 0x7fffffff; | ||
933 | } | ||
934 | |||
935 | |||
936 | static int pidfile_acquire(char *pidfile) | ||
937 | { | ||
938 | int pid_fd; | ||
939 | |||
940 | if (pidfile == NULL) { | ||
941 | return -1; | ||
942 | } | ||
943 | pid_fd = open(pidfile, O_CREAT | O_WRONLY, 0644); | ||
944 | if (pid_fd < 0) { | ||
945 | LOG(LOG_ERR, "Unable to open pidfile %s: %s\n", | ||
946 | pidfile, strerror(errno)); | ||
947 | } else { | ||
948 | lockf(pid_fd, F_LOCK, 0); | ||
949 | } | ||
950 | |||
951 | return pid_fd; | ||
952 | } | ||
953 | |||
954 | |||
955 | static void pidfile_write_release(int pid_fd) | ||
956 | { | ||
957 | FILE *out; | ||
958 | |||
959 | if (pid_fd < 0) { | ||
960 | return; | ||
961 | } | ||
962 | if ((out = fdopen(pid_fd, "w")) != NULL) { | ||
963 | fprintf(out, "%d\n", getpid()); | ||
964 | fclose(out); | ||
965 | } | ||
966 | lockf(pid_fd, F_UNLCK, 0); | ||
967 | close(pid_fd); | ||
968 | } | ||
969 | |||
970 | /* Exit and cleanup */ | ||
971 | static void exit_client(int retval) | ||
972 | { | ||
973 | unlink(client_config.pidfile); | ||
974 | if (client_config.pidfile) { | ||
975 | unlink(client_config.pidfile); | ||
976 | } | ||
977 | CLOSE_LOG(); | ||
978 | exit(retval); | ||
979 | } | ||
980 | |||
981 | |||
982 | /* SIGTERM handler */ | ||
983 | static void terminate(int sig) | ||
984 | { | ||
985 | sig = 0; | ||
986 | LOG(LOG_INFO, "Received SIGTERM"); | ||
987 | exit_client(0); | ||
988 | } | ||
989 | |||
990 | |||
991 | static inline int read_interface(char *interface, int *ifindex, | ||
992 | u_int32_t * addr, unsigned char *arp) | ||
993 | { | ||
994 | int l_fd; | ||
995 | struct ifreq ifr; | ||
996 | struct sockaddr_in *s_in; | ||
997 | |||
998 | memset(&ifr, 0, sizeof(struct ifreq)); | ||
999 | if ((l_fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) >= 0) { | ||
1000 | ifr.ifr_addr.sa_family = AF_INET; | ||
1001 | strcpy(ifr.ifr_name, interface); | ||
1002 | |||
1003 | if (addr) { | ||
1004 | if (ioctl(l_fd, SIOCGIFADDR, &ifr) == 0) { | ||
1005 | s_in = (struct sockaddr_in *) &ifr.ifr_addr; | ||
1006 | *addr = s_in->sin_addr.s_addr; | ||
1007 | DEBUG(LOG_INFO, "%s (our ip) = %s", ifr.ifr_name, | ||
1008 | inet_ntoa(s_in->sin_addr)); | ||
1009 | } else { | ||
1010 | LOG(LOG_ERR, "SIOCGIFADDR failed!: %s", strerror(errno)); | ||
1011 | return -1; | ||
1012 | } | ||
1013 | } | ||
1014 | |||
1015 | if (ioctl(l_fd, SIOCGIFINDEX, &ifr) == 0) { | ||
1016 | DEBUG(LOG_INFO, "adapter index %d", ifr.ifr_ifindex); | ||
1017 | *ifindex = ifr.ifr_ifindex; | ||
1018 | } else { | ||
1019 | LOG(LOG_ERR, "SIOCGIFINDEX failed!: %s", strerror(errno)); | ||
1020 | return -1; | ||
1021 | } | ||
1022 | if (ioctl(l_fd, SIOCGIFHWADDR, &ifr) == 0) { | ||
1023 | memcpy(arp, ifr.ifr_hwaddr.sa_data, 6); | ||
1024 | DEBUG(LOG_INFO, | ||
1025 | "adapter hardware address %02x:%02x:%02x:%02x:%02x:%02x", | ||
1026 | arp[0], arp[1], arp[2], arp[3], arp[4], arp[5]); | ||
1027 | } else { | ||
1028 | LOG(LOG_ERR, "SIOCGIFHWADDR failed!: %s", strerror(errno)); | ||
1029 | return -1; | ||
1030 | } | ||
1031 | } else { | ||
1032 | LOG(LOG_ERR, "socket failed!: %s", strerror(errno)); | ||
1033 | return -1; | ||
1034 | } | ||
1035 | close(l_fd); | ||
1036 | return 0; | ||
1037 | } | ||
1038 | |||
1039 | |||
1040 | static inline int listen_socket(unsigned int ip, int port, char *inf) | ||
1041 | { | ||
1042 | struct ifreq interface; | ||
1043 | int l_fd; | ||
1044 | struct sockaddr_in addr; | ||
1045 | int n = 1; | ||
1046 | |||
1047 | DEBUG(LOG_INFO, "Opening listen socket on 0x%08x:%d %s\n", ip, port, inf); | ||
1048 | if ((l_fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { | ||
1049 | DEBUG(LOG_ERR, "socket call failed: %s", strerror(errno)); | ||
1050 | return -1; | ||
1051 | } | ||
1052 | |||
1053 | memset(&addr, 0, sizeof(addr)); | ||
1054 | addr.sin_family = AF_INET; | ||
1055 | addr.sin_port = htons(port); | ||
1056 | addr.sin_addr.s_addr = ip; | ||
1057 | |||
1058 | if (setsockopt(l_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &n, sizeof(n)) == | ||
1059 | -1) { | ||
1060 | close(l_fd); | ||
1061 | return -1; | ||
1062 | } | ||
1063 | if (setsockopt(l_fd, SOL_SOCKET, SO_BROADCAST, (char *) &n, sizeof(n)) == | ||
1064 | -1) { | ||
1065 | close(l_fd); | ||
1066 | return -1; | ||
1067 | } | ||
1068 | |||
1069 | strncpy(interface.ifr_ifrn.ifrn_name, inf, IFNAMSIZ); | ||
1070 | if (setsockopt | ||
1071 | (l_fd, SOL_SOCKET, SO_BINDTODEVICE, (char *) &interface, | ||
1072 | sizeof(interface)) < 0) { | ||
1073 | close(l_fd); | ||
1074 | return -1; | ||
1075 | } | ||
1076 | |||
1077 | if (bind(l_fd, (struct sockaddr *) &addr, sizeof(struct sockaddr)) == -1) { | ||
1078 | close(l_fd); | ||
1079 | return -1; | ||
1080 | } | ||
1081 | |||
1082 | return l_fd; | ||
1083 | } | ||
1084 | |||
1085 | |||
1086 | static int raw_socket(int ifindex) | ||
1087 | { | ||
1088 | int l_fd; | ||
1089 | struct sockaddr_ll sock; | ||
1090 | |||
1091 | DEBUG(LOG_INFO, "Opening raw socket on ifindex %d\n", ifindex); | ||
1092 | if ((l_fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { | ||
1093 | DEBUG(LOG_ERR, "socket call failed: %s", strerror(errno)); | ||
1094 | return -1; | ||
1095 | } | ||
1096 | |||
1097 | sock.sll_family = AF_PACKET; | ||
1098 | sock.sll_protocol = htons(ETH_P_IP); | ||
1099 | sock.sll_ifindex = ifindex; | ||
1100 | if (bind(l_fd, (struct sockaddr *) &sock, sizeof(sock)) < 0) { | ||
1101 | DEBUG(LOG_ERR, "bind call failed: %s", strerror(errno)); | ||
1102 | close(l_fd); | ||
1103 | return -1; | ||
1104 | } | ||
1105 | |||
1106 | return l_fd; | ||
1107 | |||
1108 | } | ||
1109 | |||
1110 | /* read a packet from socket fd, return -1 on read error, -2 on packet error */ | ||
1111 | static int get_packet(struct dhcpMessage *packet, int l_fd) | ||
1112 | { | ||
1113 | int bytes; | ||
1114 | int i; | ||
1115 | const char broken_vendors[][8] = { | ||
1116 | "MSFT 98", | ||
1117 | "" | ||
1118 | }; | ||
1119 | char unsigned *vendor; | ||
1120 | |||
1121 | memset(packet, 0, sizeof(struct dhcpMessage)); | ||
1122 | bytes = read(l_fd, packet, sizeof(struct dhcpMessage)); | ||
1123 | if (bytes < 0) { | ||
1124 | DEBUG(LOG_INFO, "couldn't read on listening socket, ignoring"); | ||
1125 | return -1; | ||
1126 | } | ||
1127 | |||
1128 | if (ntohl(packet->cookie) != DHCP_MAGIC) { | ||
1129 | LOG(LOG_ERR, "received bogus message, ignoring"); | ||
1130 | return -2; | ||
1131 | } | ||
1132 | DEBUG(LOG_INFO, "Received a packet"); | ||
1133 | |||
1134 | if (packet->op == BOOTREQUEST | ||
1135 | && (vendor = get_option(packet, DHCP_VENDOR))) { | ||
1136 | for (i = 0; broken_vendors[i][0]; i++) { | ||
1137 | if (vendor[OPT_LEN - 2] == | ||
1138 | (unsigned char) strlen(broken_vendors[i]) | ||
1139 | && !strncmp(vendor, broken_vendors[i], vendor[OPT_LEN - 2])) { | ||
1140 | DEBUG(LOG_INFO, "broken client (%s), forcing broadcast", | ||
1141 | broken_vendors[i]); | ||
1142 | packet->flags |= htons(BROADCAST_FLAG); | ||
1143 | } | ||
1144 | } | ||
1145 | } | ||
1146 | |||
1147 | return bytes; | ||
1148 | } | ||
1149 | |||
1150 | static inline int get_raw_packet(struct dhcpMessage *payload, int l_fd) | ||
1151 | { | ||
1152 | int bytes; | ||
1153 | struct udp_dhcp_packet packet; | ||
1154 | u_int32_t source, dest; | ||
1155 | u_int16_t check; | ||
1156 | |||
1157 | memset(&packet, 0, sizeof(struct udp_dhcp_packet)); | ||
1158 | bytes = read(l_fd, &packet, sizeof(struct udp_dhcp_packet)); | ||
1159 | if (bytes < 0) { | ||
1160 | DEBUG(LOG_INFO, "couldn't read on raw listening socket -- ignoring"); | ||
1161 | usleep(500000); /* possible down interface, looping condition */ | ||
1162 | return -1; | ||
1163 | } | ||
1164 | |||
1165 | if (bytes < (int) (sizeof(struct iphdr) + sizeof(struct udphdr))) { | ||
1166 | DEBUG(LOG_INFO, "message too short, ignoring"); | ||
1167 | return -1; | ||
1168 | } | ||
1169 | |||
1170 | if (bytes < ntohs(packet.ip.tot_len)) { | ||
1171 | DEBUG(LOG_INFO, "Truncated packet"); | ||
1172 | return -1; | ||
1173 | } | ||
1174 | |||
1175 | /* ignore any extra garbage bytes */ | ||
1176 | bytes = ntohs(packet.ip.tot_len); | ||
1177 | |||
1178 | /* Make sure its the right packet for us, and that it passes sanity checks */ | ||
1179 | if (packet.ip.protocol != IPPROTO_UDP || packet.ip.version != IPVERSION || | ||
1180 | packet.ip.ihl != sizeof(packet.ip) >> 2 | ||
1181 | || packet.udp.dest != htons(CLIENT_PORT) | ||
1182 | || bytes > (int) sizeof(struct udp_dhcp_packet) | ||
1183 | || ntohs(packet.udp.len) != (short) (bytes - sizeof(packet.ip))) { | ||
1184 | DEBUG(LOG_INFO, "unrelated/bogus packet"); | ||
1185 | return -1; | ||
1186 | } | ||
1187 | |||
1188 | /* check IP checksum */ | ||
1189 | check = packet.ip.check; | ||
1190 | packet.ip.check = 0; | ||
1191 | if (check != checksum(&(packet.ip), sizeof(packet.ip))) { | ||
1192 | DEBUG(LOG_INFO, "bad IP header checksum, ignoring"); | ||
1193 | return -1; | ||
1194 | } | ||
1195 | |||
1196 | /* verify the UDP checksum by replacing the header with a psuedo header */ | ||
1197 | source = packet.ip.saddr; | ||
1198 | dest = packet.ip.daddr; | ||
1199 | check = packet.udp.check; | ||
1200 | packet.udp.check = 0; | ||
1201 | memset(&packet.ip, 0, sizeof(packet.ip)); | ||
1202 | |||
1203 | packet.ip.protocol = IPPROTO_UDP; | ||
1204 | packet.ip.saddr = source; | ||
1205 | packet.ip.daddr = dest; | ||
1206 | packet.ip.tot_len = packet.udp.len; /* cheat on the psuedo-header */ | ||
1207 | if (check && check != checksum(&packet, bytes)) { | ||
1208 | DEBUG(LOG_ERR, "packet with bad UDP checksum received, ignoring"); | ||
1209 | return -1; | ||
1210 | } | ||
1211 | |||
1212 | memcpy(payload, &(packet.data), | ||
1213 | bytes - (sizeof(packet.ip) + sizeof(packet.udp))); | ||
1214 | |||
1215 | if (ntohl(payload->cookie) != DHCP_MAGIC) { | ||
1216 | LOG(LOG_ERR, "received bogus message (bad magic) -- ignoring"); | ||
1217 | return -1; | ||
1218 | } | ||
1219 | DEBUG(LOG_INFO, "oooooh!!! got some!"); | ||
1220 | return bytes - (sizeof(packet.ip) + sizeof(packet.udp)); | ||
1221 | } | ||
1222 | |||
1223 | |||
1224 | int udhcpc_main(int argc, char *argv[]) | ||
1225 | { | ||
1226 | unsigned char *temp, *message; | ||
1227 | unsigned long t1 = 0, t2 = 0, xid = 0; | ||
1228 | unsigned long start = 0, lease; | ||
1229 | fd_set rfds; | ||
1230 | int retval; | ||
1231 | struct timeval tv; | ||
1232 | int c, len; | ||
1233 | struct dhcpMessage packet; | ||
1234 | struct in_addr temp_addr; | ||
1235 | int pid_fd; | ||
1236 | time_t now; | ||
1237 | |||
1238 | static struct option l_options[] = { | ||
1239 | {"clientid", required_argument, 0, 'c'}, | ||
1240 | {"foreground", no_argument, 0, 'f'}, | ||
1241 | {"hostname", required_argument, 0, 'H'}, | ||
1242 | {"help", no_argument, 0, 'h'}, | ||
1243 | {"interface", required_argument, 0, 'i'}, | ||
1244 | {"now", no_argument, 0, 'n'}, | ||
1245 | {"pidfile", required_argument, 0, 'p'}, | ||
1246 | {"quit", no_argument, 0, 'q'}, | ||
1247 | {"request", required_argument, 0, 'r'}, | ||
1248 | {"script", required_argument, 0, 's'}, | ||
1249 | {"version", no_argument, 0, 'v'}, | ||
1250 | {0, 0, 0, 0} | ||
1251 | }; | ||
1252 | |||
1253 | /* get options */ | ||
1254 | while (1) { | ||
1255 | int option_index = 0; | ||
1256 | |||
1257 | c = getopt_long(argc, argv, "c:fH:i:np:qr:s:v", l_options, | ||
1258 | &option_index); | ||
1259 | if (c == -1) { | ||
1260 | break; | ||
1261 | } | ||
1262 | |||
1263 | switch (c) { | ||
1264 | case 'c': | ||
1265 | len = strlen(optarg); | ||
1266 | if (len > 255) { | ||
1267 | len = 255; | ||
1268 | } | ||
1269 | if (client_config.clientid) { | ||
1270 | free(client_config.clientid); | ||
1271 | } | ||
1272 | client_config.clientid = xmalloc(len + 2); | ||
1273 | client_config.clientid[OPT_CODE] = DHCP_CLIENT_ID; | ||
1274 | client_config.clientid[OPT_LEN] = len; | ||
1275 | client_config.clientid[OPT_DATA] = '\0'; | ||
1276 | strncpy(client_config.clientid + 3, optarg, len - 1); | ||
1277 | break; | ||
1278 | case 'f': | ||
1279 | client_config.foreground = 1; | ||
1280 | break; | ||
1281 | case 'H': | ||
1282 | len = strlen(optarg); | ||
1283 | if (len > 255) { | ||
1284 | len = 255; | ||
1285 | } | ||
1286 | if (client_config.hostname) { | ||
1287 | free(client_config.hostname); | ||
1288 | } | ||
1289 | client_config.hostname = xmalloc(len + 2); | ||
1290 | client_config.hostname[OPT_CODE] = DHCP_HOST_NAME; | ||
1291 | client_config.hostname[OPT_LEN] = len; | ||
1292 | strncpy(client_config.hostname + 2, optarg, len); | ||
1293 | break; | ||
1294 | case 'i': | ||
1295 | client_config.interface = optarg; | ||
1296 | break; | ||
1297 | case 'n': | ||
1298 | client_config.abort_if_no_lease = 1; | ||
1299 | break; | ||
1300 | case 'p': | ||
1301 | client_config.pidfile = optarg; | ||
1302 | break; | ||
1303 | case 'q': | ||
1304 | client_config.quit_after_lease = 1; | ||
1305 | break; | ||
1306 | case 'r': | ||
1307 | requested_ip = inet_addr(optarg); | ||
1308 | break; | ||
1309 | case 's': | ||
1310 | client_config.script = optarg; | ||
1311 | break; | ||
1312 | case 'v': | ||
1313 | printf("udhcpcd, version %s\n\n", VERSION); | ||
1314 | exit_client(0); | ||
1315 | break; | ||
1316 | default: | ||
1317 | show_usage(); | ||
1318 | } | ||
1319 | } | ||
1320 | |||
1321 | OPEN_LOG("udhcpc"); | ||
1322 | LOG(LOG_INFO, "udhcp client (v%s) started", VERSION); | ||
1323 | |||
1324 | pid_fd = pidfile_acquire(client_config.pidfile); | ||
1325 | pidfile_write_release(pid_fd); | ||
1326 | |||
1327 | if (read_interface | ||
1328 | (client_config.interface, &client_config.ifindex, NULL, | ||
1329 | client_config.arp) < 0) { | ||
1330 | exit_client(1); | ||
1331 | } | ||
1332 | |||
1333 | if (!client_config.clientid) { | ||
1334 | client_config.clientid = xmalloc(6 + 3); | ||
1335 | client_config.clientid[OPT_CODE] = DHCP_CLIENT_ID; | ||
1336 | client_config.clientid[OPT_LEN] = 7; | ||
1337 | client_config.clientid[OPT_DATA] = 1; | ||
1338 | memcpy(client_config.clientid + 3, client_config.arp, 6); | ||
1339 | } | ||
1340 | |||
1341 | /* setup signal handlers */ | ||
1342 | signal(SIGUSR1, renew_requested); | ||
1343 | signal(SIGUSR2, release_requested); | ||
1344 | signal(SIGTERM, terminate); | ||
1345 | |||
1346 | state = INIT_SELECTING; | ||
1347 | run_script(NULL, "deconfig"); | ||
1348 | change_mode(LISTEN_RAW); | ||
1349 | |||
1350 | for (;;) { | ||
1351 | tv.tv_sec = timeout - time(0); | ||
1352 | tv.tv_usec = 0; | ||
1353 | FD_ZERO(&rfds); | ||
1354 | |||
1355 | if (listen_mode != LISTEN_NONE && fd_main < 0) { | ||
1356 | if (listen_mode == LISTEN_KERNEL) { | ||
1357 | fd_main = | ||
1358 | listen_socket(INADDR_ANY, CLIENT_PORT, | ||
1359 | client_config.interface); | ||
1360 | } else { | ||
1361 | fd_main = raw_socket(client_config.ifindex); | ||
1362 | } | ||
1363 | if (fd_main < 0) { | ||
1364 | LOG(LOG_ERR, "FATAL: couldn't listen on socket, %s", | ||
1365 | strerror(errno)); | ||
1366 | exit_client(0); | ||
1367 | } | ||
1368 | } | ||
1369 | if (fd_main >= 0) { | ||
1370 | FD_SET(fd_main, &rfds); | ||
1371 | } | ||
1372 | |||
1373 | if (tv.tv_sec > 0) { | ||
1374 | DEBUG(LOG_INFO, "Waiting on select...\n"); | ||
1375 | retval = select(fd_main + 1, &rfds, NULL, NULL, &tv); | ||
1376 | } else { | ||
1377 | retval = 0; /* If we already timed out, fall through */ | ||
1378 | } | ||
1379 | |||
1380 | now = time(0); | ||
1381 | if (retval == 0) { | ||
1382 | /* timeout dropped to zero */ | ||
1383 | switch (state) { | ||
1384 | case INIT_SELECTING: | ||
1385 | if (packet_num < 3) { | ||
1386 | if (packet_num == 0) { | ||
1387 | xid = random_xid(); | ||
1388 | } | ||
1389 | /* send discover packet */ | ||
1390 | send_discover(xid, requested_ip); /* broadcast */ | ||
1391 | |||
1392 | timeout = now + ((packet_num == 2) ? 10 : 2); | ||
1393 | packet_num++; | ||
1394 | } else { | ||
1395 | if (client_config.abort_if_no_lease) { | ||
1396 | LOG(LOG_INFO, "No lease, failing."); | ||
1397 | exit_client(1); | ||
1398 | } | ||
1399 | /* wait to try again */ | ||
1400 | packet_num = 0; | ||
1401 | timeout = now + 60; | ||
1402 | } | ||
1403 | break; | ||
1404 | case RENEW_REQUESTED: | ||
1405 | case REQUESTING: | ||
1406 | if (packet_num < 3) { | ||
1407 | /* send request packet */ | ||
1408 | if (state == RENEW_REQUESTED) { | ||
1409 | send_renew(xid, server_addr, requested_ip); /* unicast */ | ||
1410 | } else { | ||
1411 | send_selecting(xid, server_addr, requested_ip); /* broadcast */ | ||
1412 | } | ||
1413 | timeout = now + ((packet_num == 2) ? 10 : 2); | ||
1414 | packet_num++; | ||
1415 | } else { | ||
1416 | /* timed out, go back to init state */ | ||
1417 | state = INIT_SELECTING; | ||
1418 | timeout = now; | ||
1419 | packet_num = 0; | ||
1420 | change_mode(LISTEN_RAW); | ||
1421 | } | ||
1422 | break; | ||
1423 | case BOUND: | ||
1424 | /* Lease is starting to run out, time to enter renewing state */ | ||
1425 | state = RENEWING; | ||
1426 | change_mode(LISTEN_KERNEL); | ||
1427 | DEBUG(LOG_INFO, "Entering renew state"); | ||
1428 | /* fall right through */ | ||
1429 | case RENEWING: | ||
1430 | /* Either set a new T1, or enter REBINDING state */ | ||
1431 | if ((t2 - t1) <= (lease / 14400 + 1)) { | ||
1432 | /* timed out, enter rebinding state */ | ||
1433 | state = REBINDING; | ||
1434 | timeout = now + (t2 - t1); | ||
1435 | DEBUG(LOG_INFO, "Entering rebinding state"); | ||
1436 | } else { | ||
1437 | /* send a request packet */ | ||
1438 | send_renew(xid, server_addr, requested_ip); /* unicast */ | ||
1439 | |||
1440 | t1 = (t2 - t1) / 2 + t1; | ||
1441 | timeout = t1 + start; | ||
1442 | } | ||
1443 | break; | ||
1444 | case REBINDING: | ||
1445 | /* Either set a new T2, or enter INIT state */ | ||
1446 | if ((lease - t2) <= (lease / 14400 + 1)) { | ||
1447 | /* timed out, enter init state */ | ||
1448 | state = INIT_SELECTING; | ||
1449 | LOG(LOG_INFO, "Lease lost, entering init state"); | ||
1450 | run_script(NULL, "deconfig"); | ||
1451 | timeout = now; | ||
1452 | packet_num = 0; | ||
1453 | change_mode(LISTEN_RAW); | ||
1454 | } else { | ||
1455 | /* send a request packet */ | ||
1456 | send_renew(xid, 0, requested_ip); /* broadcast */ | ||
1457 | |||
1458 | t2 = (lease - t2) / 2 + t2; | ||
1459 | timeout = t2 + start; | ||
1460 | } | ||
1461 | break; | ||
1462 | case RELEASED: | ||
1463 | /* yah, I know, *you* say it would never happen */ | ||
1464 | timeout = 0x7fffffff; | ||
1465 | break; | ||
1466 | } | ||
1467 | } else if (retval > 0 && listen_mode != LISTEN_NONE | ||
1468 | && FD_ISSET(fd_main, &rfds)) { | ||
1469 | /* a packet is ready, read it */ | ||
1470 | |||
1471 | if (listen_mode == LISTEN_KERNEL) { | ||
1472 | len = get_packet(&packet, fd_main); | ||
1473 | } else { | ||
1474 | len = get_raw_packet(&packet, fd_main); | ||
1475 | } | ||
1476 | if (len == -1 && errno != EINTR) { | ||
1477 | DEBUG(LOG_INFO, "error on read, %s, reopening socket", | ||
1478 | strerror(errno)); | ||
1479 | change_mode(listen_mode); /* just close and reopen */ | ||
1480 | } | ||
1481 | if (len < 0) { | ||
1482 | continue; | ||
1483 | } | ||
1484 | |||
1485 | if (packet.xid != xid) { | ||
1486 | DEBUG(LOG_INFO, "Ignoring XID %lx (our xid is %lx)", | ||
1487 | (unsigned long) packet.xid, xid); | ||
1488 | continue; | ||
1489 | } | ||
1490 | |||
1491 | if ((message = get_option(&packet, DHCP_MESSAGE_TYPE)) == NULL) { | ||
1492 | DEBUG(LOG_ERR, "couldnt get option from packet -- ignoring"); | ||
1493 | continue; | ||
1494 | } | ||
1495 | |||
1496 | switch (state) { | ||
1497 | case INIT_SELECTING: | ||
1498 | /* Must be a DHCPOFFER to one of our xid's */ | ||
1499 | if (*message == DHCPOFFER) { | ||
1500 | if ((temp = get_option(&packet, DHCP_SERVER_ID))) { | ||
1501 | memcpy(&server_addr, temp, 4); | ||
1502 | xid = packet.xid; | ||
1503 | requested_ip = packet.yiaddr; | ||
1504 | |||
1505 | /* enter requesting state */ | ||
1506 | state = REQUESTING; | ||
1507 | timeout = now; | ||
1508 | packet_num = 0; | ||
1509 | } else { | ||
1510 | DEBUG(LOG_ERR, "No server ID in message"); | ||
1511 | } | ||
1512 | } | ||
1513 | break; | ||
1514 | case RENEW_REQUESTED: | ||
1515 | case REQUESTING: | ||
1516 | case RENEWING: | ||
1517 | case REBINDING: | ||
1518 | if (*message == DHCPACK) { | ||
1519 | if (!(temp = get_option(&packet, DHCP_LEASE_TIME))) { | ||
1520 | LOG(LOG_ERR, | ||
1521 | "No lease time with ACK, using 1 hour lease"); | ||
1522 | lease = 60 * 60; | ||
1523 | } else { | ||
1524 | memcpy(&lease, temp, 4); | ||
1525 | lease = ntohl(lease); | ||
1526 | } | ||
1527 | |||
1528 | /* enter bound state */ | ||
1529 | t1 = lease / 2; | ||
1530 | |||
1531 | /* little fixed point for n * .875 */ | ||
1532 | t2 = (lease * 0x7) >> 3; | ||
1533 | temp_addr.s_addr = packet.yiaddr; | ||
1534 | LOG(LOG_INFO, "Lease of %s obtained, lease time %ld", | ||
1535 | inet_ntoa(temp_addr), lease); | ||
1536 | start = now; | ||
1537 | timeout = t1 + start; | ||
1538 | requested_ip = packet.yiaddr; | ||
1539 | run_script(&packet, | ||
1540 | ((state == RENEWING | ||
1541 | || state == REBINDING) ? "renew" : "bound")); | ||
1542 | |||
1543 | state = BOUND; | ||
1544 | change_mode(LISTEN_NONE); | ||
1545 | { | ||
1546 | int pid_fd2; | ||
1547 | |||
1548 | if (client_config.quit_after_lease) { | ||
1549 | exit_client(0); | ||
1550 | } else if (!client_config.foreground) { | ||
1551 | pid_fd2 = pidfile_acquire(client_config.pidfile); /* hold lock during fork. */ | ||
1552 | if (daemon(0, 0) == -1) { | ||
1553 | perror("fork"); | ||
1554 | exit_client(1); | ||
1555 | } | ||
1556 | client_config.foreground = 1; /* Do not fork again. */ | ||
1557 | pidfile_write_release(pid_fd2); | ||
1558 | } | ||
1559 | } | ||
1560 | } else if (*message == DHCPNAK) { | ||
1561 | /* return to init state */ | ||
1562 | LOG(LOG_INFO, "Received DHCP NAK"); | ||
1563 | run_script(&packet, "nak"); | ||
1564 | if (state != REQUESTING) { | ||
1565 | run_script(NULL, "deconfig"); | ||
1566 | } | ||
1567 | state = INIT_SELECTING; | ||
1568 | timeout = now; | ||
1569 | requested_ip = 0; | ||
1570 | packet_num = 0; | ||
1571 | change_mode(LISTEN_RAW); | ||
1572 | sleep(3); /* avoid excessive network traffic */ | ||
1573 | } | ||
1574 | break; | ||
1575 | /* case BOUND, RELEASED: - ignore all packets */ | ||
1576 | } | ||
1577 | } else if (retval == -1 && errno == EINTR) { | ||
1578 | /* a signal was caught */ | ||
1579 | |||
1580 | } else { | ||
1581 | /* An error occured */ | ||
1582 | DEBUG(LOG_ERR, "Error on select"); | ||
1583 | } | ||
1584 | |||
1585 | } | ||
1586 | return 0; | ||
1587 | } | ||