diff options
-rw-r--r-- | networking/udhcp/d6_common.h | 118 | ||||
-rw-r--r-- | networking/udhcp/d6_dhcpc.c | 1404 | ||||
-rw-r--r-- | networking/udhcp/d6_packet.c | 168 | ||||
-rw-r--r-- | networking/udhcp/d6_socket.c | 34 |
4 files changed, 1724 insertions, 0 deletions
diff --git a/networking/udhcp/d6_common.h b/networking/udhcp/d6_common.h new file mode 100644 index 000000000..88afaf8af --- /dev/null +++ b/networking/udhcp/d6_common.h | |||
@@ -0,0 +1,118 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * Copyright (C) 2011 Denys Vlasenko. | ||
4 | * | ||
5 | * Licensed under GPLv2, see file LICENSE in this source tree. | ||
6 | */ | ||
7 | #ifndef UDHCP_D6_COMMON_H | ||
8 | #define UDHCP_D6_COMMON_H 1 | ||
9 | |||
10 | #include <netinet/ip6.h> | ||
11 | |||
12 | PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN | ||
13 | |||
14 | |||
15 | /*** DHCPv6 packet ***/ | ||
16 | |||
17 | /* DHCPv6 protocol. See RFC 3315 */ | ||
18 | #define D6_MSG_SOLICIT 1 | ||
19 | #define D6_MSG_ADVERTISE 2 | ||
20 | #define D6_MSG_REQUEST 3 | ||
21 | #define D6_MSG_CONFIRM 4 | ||
22 | #define D6_MSG_RENEW 5 | ||
23 | #define D6_MSG_REBIND 6 | ||
24 | #define D6_MSG_REPLY 7 | ||
25 | #define D6_MSG_RELEASE 8 | ||
26 | #define D6_MSG_DECLINE 9 | ||
27 | #define D6_MSG_RECONFIGURE 10 | ||
28 | #define D6_MSG_INFORMATION_REQUEST 11 | ||
29 | #define D6_MSG_RELAY_FORW 12 | ||
30 | #define D6_MSG_RELAY_REPL 13 | ||
31 | |||
32 | struct d6_packet { | ||
33 | union { | ||
34 | uint8_t d6_msg_type; | ||
35 | uint32_t d6_xid32; | ||
36 | } d6_u; | ||
37 | uint8_t d6_options[576 - sizeof(struct iphdr) - sizeof(struct udphdr) - 4 | ||
38 | + CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS]; | ||
39 | } PACKED; | ||
40 | #define d6_msg_type d6_u.d6_msg_type | ||
41 | #define d6_xid32 d6_u.d6_xid32 | ||
42 | |||
43 | struct ip6_udp_d6_packet { | ||
44 | struct ip6_hdr ip6; | ||
45 | struct udphdr udp; | ||
46 | struct d6_packet data; | ||
47 | } PACKED; | ||
48 | |||
49 | struct udp_d6_packet { | ||
50 | struct udphdr udp; | ||
51 | struct d6_packet data; | ||
52 | } PACKED; | ||
53 | |||
54 | /*** Options ***/ | ||
55 | |||
56 | struct d6_option { | ||
57 | uint8_t code; | ||
58 | uint8_t code_hi; | ||
59 | uint8_t len; | ||
60 | uint8_t len_hi; | ||
61 | uint8_t data[1]; | ||
62 | } PACKED; | ||
63 | |||
64 | #define D6_OPT_CLIENTID 1 | ||
65 | #define D6_OPT_SERVERID 2 | ||
66 | #define D6_OPT_IA_NA 3 | ||
67 | #define D6_OPT_IA_TA 4 | ||
68 | #define D6_OPT_IAADDR 5 | ||
69 | #define D6_OPT_ORO 6 | ||
70 | #define D6_OPT_PREFERENCE 7 | ||
71 | #define D6_OPT_ELAPSED_TIME 8 | ||
72 | #define D6_OPT_RELAY_MSG 9 | ||
73 | #define D6_OPT_AUTH 11 | ||
74 | #define D6_OPT_UNICAST 12 | ||
75 | #define D6_OPT_STATUS_CODE 13 | ||
76 | #define D6_OPT_RAPID_COMMIT 14 | ||
77 | #define D6_OPT_USER_CLASS 15 | ||
78 | #define D6_OPT_VENDOR_CLASS 16 | ||
79 | #define D6_OPT_VENDOR_OPTS 17 | ||
80 | #define D6_OPT_INTERFACE_ID 18 | ||
81 | #define D6_OPT_RECONF_MSG 19 | ||
82 | #define D6_OPT_RECONF_ACCEPT 20 | ||
83 | |||
84 | /*** Other shared functions ***/ | ||
85 | |||
86 | struct client6_data_t { | ||
87 | struct d6_option *server_id; | ||
88 | struct d6_option *ia_na; | ||
89 | }; | ||
90 | |||
91 | #define client6_data (*(struct client6_data_t*)(&bb_common_bufsiz1[COMMON_BUFSIZE - sizeof(struct client6_data_t)])) | ||
92 | |||
93 | int FAST_FUNC d6_listen_socket(int port, const char *inf); | ||
94 | |||
95 | int FAST_FUNC d6_recv_kernel_packet( | ||
96 | struct in6_addr *peer_ipv6, | ||
97 | struct d6_packet *packet, int fd | ||
98 | ); | ||
99 | |||
100 | int FAST_FUNC d6_send_raw_packet( | ||
101 | struct d6_packet *d6_pkt, unsigned d6_pkt_size, | ||
102 | struct in6_addr *src_ipv6, int source_port, | ||
103 | struct in6_addr *dst_ipv6, int dest_port, const uint8_t *dest_arp, | ||
104 | int ifindex | ||
105 | ); | ||
106 | |||
107 | int FAST_FUNC d6_send_kernel_packet( | ||
108 | struct d6_packet *d6_pkt, unsigned d6_pkt_size, | ||
109 | struct in6_addr *src_ipv6, int source_port, | ||
110 | struct in6_addr *dst_ipv6, int dest_port | ||
111 | ); | ||
112 | |||
113 | void FAST_FUNC d6_dump_packet(struct d6_packet *packet); | ||
114 | |||
115 | |||
116 | POP_SAVED_FUNCTION_VISIBILITY | ||
117 | |||
118 | #endif | ||
diff --git a/networking/udhcp/d6_dhcpc.c b/networking/udhcp/d6_dhcpc.c new file mode 100644 index 000000000..d1baaae9b --- /dev/null +++ b/networking/udhcp/d6_dhcpc.c | |||
@@ -0,0 +1,1404 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * DHCPv6 client. | ||
4 | * | ||
5 | * 2011-11. | ||
6 | * WARNING: THIS CODE IS INCOMPLETE. IT IS NOWHERE NEAR | ||
7 | * TO BE READY FOR PRODUCTION USE. | ||
8 | * | ||
9 | * Copyright (C) 2011 Denys Vlasenko. | ||
10 | * | ||
11 | * Licensed under GPLv2, see file LICENSE in this source tree. | ||
12 | */ | ||
13 | |||
14 | //config:config UDHCPC6 | ||
15 | //config: bool "udhcp client for DHCPv6 (udhcpc6)" | ||
16 | //config: default n # not yet ready | ||
17 | //config: help | ||
18 | //config: udhcpc6 is a DHCPv6 client | ||
19 | |||
20 | //applet:IF_UDHCPC6(APPLET(udhcpc6, BB_DIR_USR_BIN, BB_SUID_DROP)) | ||
21 | |||
22 | //kbuild:lib-$(CONFIG_UDHCPC6) += d6_dhcpc.o d6_packet.o d6_socket.o common.o | ||
23 | |||
24 | |||
25 | #include <syslog.h> | ||
26 | /* Override ENABLE_FEATURE_PIDFILE - ifupdown needs our pidfile to always exist */ | ||
27 | #define WANT_PIDFILE 1 | ||
28 | #include "common.h" | ||
29 | #include "dhcpd.h" | ||
30 | #include "dhcpc.h" | ||
31 | #include "d6_common.h" | ||
32 | |||
33 | #include <netinet/if_ether.h> | ||
34 | #include <netpacket/packet.h> | ||
35 | #include <linux/filter.h> | ||
36 | |||
37 | /* "struct client_config_t client_config" is in bb_common_bufsiz1 */ | ||
38 | |||
39 | |||
40 | #if ENABLE_LONG_OPTS | ||
41 | static const char udhcpc_longopts[] ALIGN1 = | ||
42 | "interface\0" Required_argument "i" | ||
43 | "now\0" No_argument "n" | ||
44 | "pidfile\0" Required_argument "p" | ||
45 | "quit\0" No_argument "q" | ||
46 | "release\0" No_argument "R" | ||
47 | "request\0" Required_argument "r" | ||
48 | "script\0" Required_argument "s" | ||
49 | "timeout\0" Required_argument "T" | ||
50 | "version\0" No_argument "v" | ||
51 | "retries\0" Required_argument "t" | ||
52 | "tryagain\0" Required_argument "A" | ||
53 | "syslog\0" No_argument "S" | ||
54 | "request-option\0" Required_argument "O" | ||
55 | "no-default-options\0" No_argument "o" | ||
56 | "foreground\0" No_argument "f" | ||
57 | "background\0" No_argument "b" | ||
58 | "broadcast\0" No_argument "B" | ||
59 | /// IF_FEATURE_UDHCPC_ARPING("arping\0" No_argument "a") | ||
60 | IF_FEATURE_UDHCP_PORT("client-port\0" Required_argument "P") | ||
61 | ; | ||
62 | #endif | ||
63 | /* Must match getopt32 option string order */ | ||
64 | enum { | ||
65 | OPT_i = 1 << 0, | ||
66 | OPT_n = 1 << 1, | ||
67 | OPT_p = 1 << 2, | ||
68 | OPT_q = 1 << 3, | ||
69 | OPT_R = 1 << 4, | ||
70 | OPT_r = 1 << 5, | ||
71 | OPT_s = 1 << 6, | ||
72 | OPT_T = 1 << 7, | ||
73 | OPT_t = 1 << 8, | ||
74 | OPT_S = 1 << 9, | ||
75 | OPT_A = 1 << 10, | ||
76 | OPT_O = 1 << 11, | ||
77 | OPT_o = 1 << 12, | ||
78 | OPT_x = 1 << 13, | ||
79 | OPT_f = 1 << 14, | ||
80 | OPT_B = 1 << 15, | ||
81 | /* The rest has variable bit positions, need to be clever */ | ||
82 | OPTBIT_B = 15, | ||
83 | USE_FOR_MMU( OPTBIT_b,) | ||
84 | ///IF_FEATURE_UDHCPC_ARPING(OPTBIT_a,) | ||
85 | IF_FEATURE_UDHCP_PORT( OPTBIT_P,) | ||
86 | USE_FOR_MMU( OPT_b = 1 << OPTBIT_b,) | ||
87 | ///IF_FEATURE_UDHCPC_ARPING(OPT_a = 1 << OPTBIT_a,) | ||
88 | IF_FEATURE_UDHCP_PORT( OPT_P = 1 << OPTBIT_P,) | ||
89 | }; | ||
90 | |||
91 | |||
92 | /*** Utility functions ***/ | ||
93 | |||
94 | static void *d6_find_option(uint8_t *option, uint8_t *option_end, unsigned code) | ||
95 | { | ||
96 | /* "length minus 4" */ | ||
97 | int len_m4 = option_end - option - 4; | ||
98 | while (len_m4 >= 0) { | ||
99 | /* Next option's len is too big? */ | ||
100 | if (option[2] > len_m4) | ||
101 | return NULL; /* yes. bogus packet! */ | ||
102 | /* So far we treat any opts with code >255 | ||
103 | * or len >255 as bogus, and stop at once. | ||
104 | * This simplifies big-endian handling. | ||
105 | */ | ||
106 | if (option[1] != 0 || option[3] != 0) | ||
107 | return NULL; | ||
108 | /* Option seems to be valid */ | ||
109 | /* Does its code match? */ | ||
110 | if (option[0] == code) | ||
111 | return option; /* yes! */ | ||
112 | option += option[2] + 4; | ||
113 | len_m4 -= option[2] + 4; | ||
114 | } | ||
115 | return NULL; | ||
116 | } | ||
117 | |||
118 | static void *d6_copy_option(uint8_t *option, uint8_t *option_end, unsigned code) | ||
119 | { | ||
120 | uint8_t *opt = d6_find_option(option, option_end, code); | ||
121 | if (!opt) | ||
122 | return opt; | ||
123 | return memcpy(xmalloc(opt[2] + 4), opt, opt[2] + 4); | ||
124 | } | ||
125 | |||
126 | static void *d6_store_blob(void *dst, const void *src, unsigned len) | ||
127 | { | ||
128 | memcpy(dst, src, len); | ||
129 | return dst + len; | ||
130 | } | ||
131 | |||
132 | |||
133 | /*** Script execution code ***/ | ||
134 | |||
135 | /* put all the parameters into the environment */ | ||
136 | static char **fill_envp(struct d6_packet *packet | ||
137 | UNUSED_PARAM | ||
138 | ) | ||
139 | { | ||
140 | int envc; | ||
141 | char **envp, **curr; | ||
142 | |||
143 | #define BITMAP unsigned | ||
144 | #define BBITS (sizeof(BITMAP) * 8) | ||
145 | #define BMASK(i) (1 << (i & (sizeof(BITMAP) * 8 - 1))) | ||
146 | #define FOUND_OPTS(i) (found_opts[(unsigned)i / BBITS]) | ||
147 | ///BITMAP found_opts[256 / BBITS]; | ||
148 | |||
149 | ///memset(found_opts, 0, sizeof(found_opts)); | ||
150 | |||
151 | /* We need 2 elements for: | ||
152 | * "interface=IFACE" | ||
153 | * terminating NULL | ||
154 | */ | ||
155 | envc = 2; | ||
156 | |||
157 | curr = envp = xzalloc(sizeof(envp[0]) * envc); | ||
158 | |||
159 | *curr = xasprintf("interface=%s", client_config.interface); | ||
160 | putenv(*curr++); | ||
161 | |||
162 | return envp; | ||
163 | } | ||
164 | |||
165 | /* Call a script with a par file and env vars */ | ||
166 | static void d6_run_script(struct d6_packet *packet, const char *name) | ||
167 | { | ||
168 | char **envp, **curr; | ||
169 | char *argv[3]; | ||
170 | |||
171 | envp = fill_envp(packet); | ||
172 | |||
173 | /* call script */ | ||
174 | log1("Executing %s %s", client_config.script, name); | ||
175 | argv[0] = (char*) client_config.script; | ||
176 | argv[1] = (char*) name; | ||
177 | argv[2] = NULL; | ||
178 | spawn_and_wait(argv); | ||
179 | |||
180 | for (curr = envp; *curr; curr++) { | ||
181 | log2(" %s", *curr); | ||
182 | bb_unsetenv_and_free(*curr); | ||
183 | } | ||
184 | free(envp); | ||
185 | } | ||
186 | |||
187 | |||
188 | /*** Sending/receiving packets ***/ | ||
189 | |||
190 | static ALWAYS_INLINE uint32_t random_xid(void) | ||
191 | { | ||
192 | uint32_t t = rand() & htonl(0x00ffffff); | ||
193 | return t; | ||
194 | } | ||
195 | |||
196 | /* Initialize the packet with the proper defaults */ | ||
197 | static uint8_t *init_d6_packet(struct d6_packet *packet, char type, uint32_t xid) | ||
198 | { | ||
199 | struct d6_option *clientid; | ||
200 | |||
201 | memset(packet, 0, sizeof(*packet)); | ||
202 | |||
203 | packet->d6_xid32 = xid; | ||
204 | packet->d6_msg_type = type; | ||
205 | |||
206 | clientid = (void*)client_config.clientid; | ||
207 | return d6_store_blob(packet->d6_options, clientid, clientid->len + 2+2); | ||
208 | } | ||
209 | |||
210 | static uint8_t *add_d6_client_options(uint8_t *ptr) | ||
211 | { | ||
212 | return ptr; | ||
213 | //uint8_t c; | ||
214 | //int i, end, len; | ||
215 | |||
216 | /* Add a "param req" option with the list of options we'd like to have | ||
217 | * from stubborn DHCP servers. Pull the data from the struct in common.c. | ||
218 | * No bounds checking because it goes towards the head of the packet. */ | ||
219 | //... | ||
220 | |||
221 | /* Add -x options if any */ | ||
222 | //... | ||
223 | } | ||
224 | |||
225 | static int d6_mcast_from_client_config_ifindex(struct d6_packet *packet, uint8_t *end) | ||
226 | { | ||
227 | static const uint8_t FF02__1_2[16] = { | ||
228 | 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
229 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, | ||
230 | }; | ||
231 | |||
232 | return d6_send_raw_packet( | ||
233 | packet, (end - (uint8_t*) packet), | ||
234 | /*src*/ NULL, CLIENT_PORT, | ||
235 | /*dst*/ (struct in6_addr*)FF02__1_2, SERVER_PORT, MAC_BCAST_ADDR, | ||
236 | client_config.ifindex | ||
237 | ); | ||
238 | } | ||
239 | |||
240 | /* Milticast a DHCPv6 Solicit packet to the network, with an optionally requested IP. | ||
241 | * | ||
242 | * RFC 3315 17.1.1. Creation of Solicit Messages | ||
243 | * | ||
244 | * The client MUST include a Client Identifier option to identify itself | ||
245 | * to the server. The client includes IA options for any IAs to which | ||
246 | * it wants the server to assign addresses. The client MAY include | ||
247 | * addresses in the IAs as a hint to the server about addresses for | ||
248 | * which the client has a preference. ... | ||
249 | * | ||
250 | * The client uses IA_NA options to request the assignment of non- | ||
251 | * temporary addresses and uses IA_TA options to request the assignment | ||
252 | * of temporary addresses. Either IA_NA or IA_TA options, or a | ||
253 | * combination of both, can be included in DHCP messages. | ||
254 | * | ||
255 | * The client SHOULD include an Option Request option (see section 22.7) | ||
256 | * to indicate the options the client is interested in receiving. The | ||
257 | * client MAY additionally include instances of those options that are | ||
258 | * identified in the Option Request option, with data values as hints to | ||
259 | * the server about parameter values the client would like to have | ||
260 | * returned. | ||
261 | * | ||
262 | * The client includes a Reconfigure Accept option (see section 22.20) | ||
263 | * if the client is willing to accept Reconfigure messages from the | ||
264 | * server. | ||
265 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
266 | | OPTION_CLIENTID | option-len | | ||
267 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
268 | . . | ||
269 | . DUID . | ||
270 | . (variable length) . | ||
271 | . . | ||
272 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
273 | |||
274 | |||
275 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
276 | | OPTION_IA_NA | option-len | | ||
277 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
278 | | IAID (4 octets) | | ||
279 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
280 | | T1 | | ||
281 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
282 | | T2 | | ||
283 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
284 | | | | ||
285 | . IA_NA-options . | ||
286 | . . | ||
287 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
288 | |||
289 | |||
290 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
291 | | OPTION_IAADDR | option-len | | ||
292 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
293 | | | | ||
294 | | IPv6 address | | ||
295 | | | | ||
296 | | | | ||
297 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
298 | | preferred-lifetime | | ||
299 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
300 | | valid-lifetime | | ||
301 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
302 | . . | ||
303 | . IAaddr-options . | ||
304 | . . | ||
305 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
306 | |||
307 | |||
308 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
309 | | OPTION_ORO | option-len | | ||
310 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
311 | | requested-option-code-1 | requested-option-code-2 | | ||
312 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
313 | | ... | | ||
314 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
315 | |||
316 | |||
317 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
318 | | OPTION_RECONF_ACCEPT | 0 | | ||
319 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
320 | */ | ||
321 | /* NOINLINE: limit stack usage in caller */ | ||
322 | static NOINLINE int send_d6_discover(uint32_t xid, struct in6_addr *requested_ipv6) | ||
323 | { | ||
324 | struct d6_packet packet; | ||
325 | uint8_t *opt_ptr; | ||
326 | unsigned len; | ||
327 | |||
328 | /* Fill in: msg type, client id */ | ||
329 | opt_ptr = init_d6_packet(&packet, D6_MSG_SOLICIT, xid); | ||
330 | |||
331 | /* Create new IA_NA, optionally with included IAADDR with requested IP */ | ||
332 | free(client6_data.ia_na); | ||
333 | len = requested_ipv6 ? 2+2+4+4+4 + 2+2+16+4+4 : 2+2+4+4+4; | ||
334 | client6_data.ia_na = xzalloc(len); | ||
335 | client6_data.ia_na->code = D6_OPT_IA_NA; | ||
336 | client6_data.ia_na->len = len - 4; | ||
337 | *(uint32_t*)client6_data.ia_na->data = rand(); /* IAID */ | ||
338 | if (requested_ipv6) { | ||
339 | struct d6_option *iaaddr = (void*)(client6_data.ia_na->data + 4+4+4); | ||
340 | iaaddr->code = D6_OPT_IAADDR; | ||
341 | iaaddr->len = 16+4+4; | ||
342 | memcpy(iaaddr->data, requested_ipv6, 16); | ||
343 | } | ||
344 | opt_ptr = d6_store_blob(opt_ptr, client6_data.ia_na, len); | ||
345 | |||
346 | /* Add options: | ||
347 | * "param req" option according to -O, options specified with -x | ||
348 | */ | ||
349 | opt_ptr = add_d6_client_options(opt_ptr); | ||
350 | |||
351 | bb_info_msg("Sending discover..."); | ||
352 | return d6_mcast_from_client_config_ifindex(&packet, opt_ptr); | ||
353 | } | ||
354 | |||
355 | /* Multicast a DHCPv6 request message | ||
356 | * | ||
357 | * RFC 3315 18.1.1. Creation and Transmission of Request Messages | ||
358 | * | ||
359 | * The client uses a Request message to populate IAs with addresses and | ||
360 | * obtain other configuration information. The client includes one or | ||
361 | * more IA options in the Request message. The server then returns | ||
362 | * addresses and other information about the IAs to the client in IA | ||
363 | * options in a Reply message. | ||
364 | * | ||
365 | * The client generates a transaction ID and inserts this value in the | ||
366 | * "transaction-id" field. | ||
367 | * | ||
368 | * The client places the identifier of the destination server in a | ||
369 | * Server Identifier option. | ||
370 | * | ||
371 | * The client MUST include a Client Identifier option to identify itself | ||
372 | * to the server. The client adds any other appropriate options, | ||
373 | * including one or more IA options (if the client is requesting that | ||
374 | * the server assign it some network addresses). | ||
375 | * | ||
376 | * The client MUST include an Option Request option (see section 22.7) | ||
377 | * to indicate the options the client is interested in receiving. The | ||
378 | * client MAY include options with data values as hints to the server | ||
379 | * about parameter values the client would like to have returned. | ||
380 | * | ||
381 | * The client includes a Reconfigure Accept option (see section 22.20) | ||
382 | * indicating whether or not the client is willing to accept Reconfigure | ||
383 | * messages from the server. | ||
384 | */ | ||
385 | /* NOINLINE: limit stack usage in caller */ | ||
386 | static NOINLINE int send_d6_select(uint32_t xid) | ||
387 | { | ||
388 | struct d6_packet packet; | ||
389 | uint8_t *opt_ptr; | ||
390 | |||
391 | /* Fill in: msg type, client id */ | ||
392 | opt_ptr = init_d6_packet(&packet, D6_MSG_REQUEST, xid); | ||
393 | |||
394 | /* server id */ | ||
395 | opt_ptr = d6_store_blob(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2); | ||
396 | /* IA NA (contains requested IP) */ | ||
397 | opt_ptr = d6_store_blob(opt_ptr, client6_data.ia_na, client6_data.ia_na->len + 2+2); | ||
398 | |||
399 | /* Add options: | ||
400 | * "param req" option according to -O, options specified with -x | ||
401 | */ | ||
402 | opt_ptr = add_d6_client_options(opt_ptr); | ||
403 | |||
404 | bb_info_msg("Sending select..."); | ||
405 | return d6_mcast_from_client_config_ifindex(&packet, opt_ptr); | ||
406 | } | ||
407 | |||
408 | /* Unicast or broadcast a DHCP renew message | ||
409 | * | ||
410 | * RFC 3315 18.1.3. Creation and Transmission of Renew Messages | ||
411 | * | ||
412 | * To extend the valid and preferred lifetimes for the addresses | ||
413 | * associated with an IA, the client sends a Renew message to the server | ||
414 | * from which the client obtained the addresses in the IA containing an | ||
415 | * IA option for the IA. The client includes IA Address options in the | ||
416 | * IA option for the addresses associated with the IA. The server | ||
417 | * determines new lifetimes for the addresses in the IA according to the | ||
418 | * administrative configuration of the server. The server may also add | ||
419 | * new addresses to the IA. The server may remove addresses from the IA | ||
420 | * by setting the preferred and valid lifetimes of those addresses to | ||
421 | * zero. | ||
422 | * | ||
423 | * The server controls the time at which the client contacts the server | ||
424 | * to extend the lifetimes on assigned addresses through the T1 and T2 | ||
425 | * parameters assigned to an IA. | ||
426 | * | ||
427 | * At time T1 for an IA, the client initiates a Renew/Reply message | ||
428 | * exchange to extend the lifetimes on any addresses in the IA. The | ||
429 | * client includes an IA option with all addresses currently assigned to | ||
430 | * the IA in its Renew message. | ||
431 | * | ||
432 | * If T1 or T2 is set to 0 by the server (for an IA_NA) or there are no | ||
433 | * T1 or T2 times (for an IA_TA), the client may send a Renew or Rebind | ||
434 | * message, respectively, at the client's discretion. | ||
435 | * | ||
436 | * The client sets the "msg-type" field to RENEW. The client generates | ||
437 | * a transaction ID and inserts this value in the "transaction-id" | ||
438 | * field. | ||
439 | * | ||
440 | * The client places the identifier of the destination server in a | ||
441 | * Server Identifier option. | ||
442 | * | ||
443 | * The client MUST include a Client Identifier option to identify itself | ||
444 | * to the server. The client adds any appropriate options, including | ||
445 | * one or more IA options. The client MUST include the list of | ||
446 | * addresses the client currently has associated with the IAs in the | ||
447 | * Renew message. | ||
448 | * | ||
449 | * The client MUST include an Option Request option (see section 22.7) | ||
450 | * to indicate the options the client is interested in receiving. The | ||
451 | * client MAY include options with data values as hints to the server | ||
452 | * about parameter values the client would like to have returned. | ||
453 | */ | ||
454 | /* NOINLINE: limit stack usage in caller */ | ||
455 | static NOINLINE int send_d6_renew(uint32_t xid, struct in6_addr *server_ipv6, struct in6_addr *our_cur_ipv6) | ||
456 | { | ||
457 | struct d6_packet packet; | ||
458 | uint8_t *opt_ptr; | ||
459 | |||
460 | /* Fill in: msg type, client id */ | ||
461 | opt_ptr = init_d6_packet(&packet, DHCPREQUEST, xid); | ||
462 | |||
463 | /* server id */ | ||
464 | opt_ptr = d6_store_blob(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2); | ||
465 | /* IA NA (contains requested IP) */ | ||
466 | opt_ptr = d6_store_blob(opt_ptr, client6_data.ia_na, client6_data.ia_na->len + 2+2); | ||
467 | |||
468 | /* Add options: | ||
469 | * "param req" option according to -O, options specified with -x | ||
470 | */ | ||
471 | opt_ptr = add_d6_client_options(opt_ptr); | ||
472 | |||
473 | bb_info_msg("Sending renew..."); | ||
474 | if (server_ipv6) | ||
475 | return d6_send_kernel_packet( | ||
476 | &packet, (opt_ptr - (uint8_t*) &packet), | ||
477 | our_cur_ipv6, CLIENT_PORT, | ||
478 | server_ipv6, SERVER_PORT | ||
479 | ); | ||
480 | return d6_mcast_from_client_config_ifindex(&packet, opt_ptr); | ||
481 | } | ||
482 | |||
483 | /* Unicast a DHCP release message */ | ||
484 | static int send_d6_release(struct in6_addr *server_ipv6, struct in6_addr *our_cur_ipv6) | ||
485 | { | ||
486 | struct d6_packet packet; | ||
487 | uint8_t *opt_ptr; | ||
488 | |||
489 | /* Fill in: msg type, client id */ | ||
490 | opt_ptr = init_d6_packet(&packet, D6_MSG_RELEASE, random_xid()); | ||
491 | /* server id */ | ||
492 | opt_ptr = d6_store_blob(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2); | ||
493 | /* IA NA (contains our current IP) */ | ||
494 | opt_ptr = d6_store_blob(opt_ptr, client6_data.ia_na, client6_data.ia_na->len + 2+2); | ||
495 | |||
496 | bb_info_msg("Sending release..."); | ||
497 | return d6_send_kernel_packet( | ||
498 | &packet, (opt_ptr - (uint8_t*) &packet), | ||
499 | our_cur_ipv6, CLIENT_PORT, | ||
500 | server_ipv6, SERVER_PORT | ||
501 | ); | ||
502 | } | ||
503 | |||
504 | /* Returns -1 on errors that are fatal for the socket, -2 for those that aren't */ | ||
505 | /* NOINLINE: limit stack usage in caller */ | ||
506 | static NOINLINE int d6_recv_raw_packet(struct in6_addr *peer_ipv6 | ||
507 | UNUSED_PARAM | ||
508 | , struct d6_packet *d6_pkt, int fd) | ||
509 | { | ||
510 | int bytes; | ||
511 | struct ip6_udp_d6_packet packet; | ||
512 | |||
513 | bytes = safe_read(fd, &packet, sizeof(packet)); | ||
514 | if (bytes < 0) { | ||
515 | log1("Packet read error, ignoring"); | ||
516 | /* NB: possible down interface, etc. Caller should pause. */ | ||
517 | return bytes; /* returns -1 */ | ||
518 | } | ||
519 | |||
520 | if (bytes < (int) (sizeof(packet.ip6) + sizeof(packet.udp))) { | ||
521 | log1("Packet is too short, ignoring"); | ||
522 | return -2; | ||
523 | } | ||
524 | |||
525 | if (bytes < sizeof(packet.ip6) + ntohs(packet.ip6.ip6_plen)) { | ||
526 | /* packet is bigger than sizeof(packet), we did partial read */ | ||
527 | log1("Oversized packet, ignoring"); | ||
528 | return -2; | ||
529 | } | ||
530 | |||
531 | /* ignore any extra garbage bytes */ | ||
532 | bytes = sizeof(packet.ip6) + ntohs(packet.ip6.ip6_plen); | ||
533 | |||
534 | /* make sure its the right packet for us, and that it passes sanity checks */ | ||
535 | if (packet.ip6.ip6_nxt != IPPROTO_UDP | ||
536 | || (packet.ip6.ip6_vfc >> 4) != 6 | ||
537 | || packet.udp.dest != htons(CLIENT_PORT) | ||
538 | /* || bytes > (int) sizeof(packet) - can't happen */ | ||
539 | || packet.udp.len != packet.ip6.ip6_plen | ||
540 | ) { | ||
541 | log1("Unrelated/bogus packet, ignoring"); | ||
542 | return -2; | ||
543 | } | ||
544 | |||
545 | //How to do this for ipv6? | ||
546 | // /* verify UDP checksum. IP header has to be modified for this */ | ||
547 | // memset(&packet.ip, 0, offsetof(struct iphdr, protocol)); | ||
548 | // /* ip.xx fields which are not memset: protocol, check, saddr, daddr */ | ||
549 | // packet.ip.tot_len = packet.udp.len; /* yes, this is needed */ | ||
550 | // check = packet.udp.check; | ||
551 | // packet.udp.check = 0; | ||
552 | // if (check && check != inet_cksum((uint16_t *)&packet, bytes)) { | ||
553 | // log1("Packet with bad UDP checksum received, ignoring"); | ||
554 | // return -2; | ||
555 | // } | ||
556 | |||
557 | log1("Received a packet"); | ||
558 | d6_dump_packet(&packet.data); | ||
559 | |||
560 | bytes -= sizeof(packet.ip6) + sizeof(packet.udp); | ||
561 | memcpy(d6_pkt, &packet.data, bytes); | ||
562 | return bytes; | ||
563 | } | ||
564 | |||
565 | |||
566 | /*** Main ***/ | ||
567 | |||
568 | static int sockfd = -1; | ||
569 | |||
570 | #define LISTEN_NONE 0 | ||
571 | #define LISTEN_KERNEL 1 | ||
572 | #define LISTEN_RAW 2 | ||
573 | static smallint listen_mode; | ||
574 | |||
575 | /* initial state: (re)start DHCP negotiation */ | ||
576 | #define INIT_SELECTING 0 | ||
577 | /* discover was sent, DHCPOFFER reply received */ | ||
578 | #define REQUESTING 1 | ||
579 | /* select/renew was sent, DHCPACK reply received */ | ||
580 | #define BOUND 2 | ||
581 | /* half of lease passed, want to renew it by sending unicast renew requests */ | ||
582 | #define RENEWING 3 | ||
583 | /* renew requests were not answered, lease is almost over, send broadcast renew */ | ||
584 | #define REBINDING 4 | ||
585 | /* manually requested renew (SIGUSR1) */ | ||
586 | #define RENEW_REQUESTED 5 | ||
587 | /* release, possibly manually requested (SIGUSR2) */ | ||
588 | #define RELEASED 6 | ||
589 | static smallint state; | ||
590 | |||
591 | static int d6_raw_socket(int ifindex) | ||
592 | { | ||
593 | int fd; | ||
594 | struct sockaddr_ll sock; | ||
595 | |||
596 | /* | ||
597 | * Comment: | ||
598 | * | ||
599 | * I've selected not to see LL header, so BPF doesn't see it, too. | ||
600 | * The filter may also pass non-IP and non-ARP packets, but we do | ||
601 | * a more complete check when receiving the message in userspace. | ||
602 | * | ||
603 | * and filter shamelessly stolen from: | ||
604 | * | ||
605 | * http://www.flamewarmaster.de/software/dhcpclient/ | ||
606 | * | ||
607 | * There are a few other interesting ideas on that page (look under | ||
608 | * "Motivation"). Use of netlink events is most interesting. Think | ||
609 | * of various network servers listening for events and reconfiguring. | ||
610 | * That would obsolete sending HUP signals and/or make use of restarts. | ||
611 | * | ||
612 | * Copyright: 2006, 2007 Stefan Rompf <sux@loplof.de>. | ||
613 | * License: GPL v2. | ||
614 | * | ||
615 | * TODO: make conditional? | ||
616 | */ | ||
617 | #if 0 | ||
618 | static const struct sock_filter filter_instr[] = { | ||
619 | /* load 9th byte (protocol) */ | ||
620 | BPF_STMT(BPF_LD|BPF_B|BPF_ABS, 9), | ||
621 | /* jump to L1 if it is IPPROTO_UDP, else to L4 */ | ||
622 | BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, IPPROTO_UDP, 0, 6), | ||
623 | /* L1: load halfword from offset 6 (flags and frag offset) */ | ||
624 | BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 6), | ||
625 | /* jump to L4 if any bits in frag offset field are set, else to L2 */ | ||
626 | BPF_JUMP(BPF_JMP|BPF_JSET|BPF_K, 0x1fff, 4, 0), | ||
627 | /* L2: skip IP header (load index reg with header len) */ | ||
628 | BPF_STMT(BPF_LDX|BPF_B|BPF_MSH, 0), | ||
629 | /* load udp destination port from halfword[header_len + 2] */ | ||
630 | BPF_STMT(BPF_LD|BPF_H|BPF_IND, 2), | ||
631 | /* jump to L3 if udp dport is CLIENT_PORT, else to L4 */ | ||
632 | BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 68, 0, 1), | ||
633 | /* L3: accept packet */ | ||
634 | BPF_STMT(BPF_RET|BPF_K, 0xffffffff), | ||
635 | /* L4: discard packet */ | ||
636 | BPF_STMT(BPF_RET|BPF_K, 0), | ||
637 | }; | ||
638 | static const struct sock_fprog filter_prog = { | ||
639 | .len = sizeof(filter_instr) / sizeof(filter_instr[0]), | ||
640 | /* casting const away: */ | ||
641 | .filter = (struct sock_filter *) filter_instr, | ||
642 | }; | ||
643 | #endif | ||
644 | |||
645 | log1("Opening raw socket on ifindex %d", ifindex); //log2? | ||
646 | |||
647 | fd = xsocket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6)); | ||
648 | log1("Got raw socket fd %d", fd); //log2? | ||
649 | |||
650 | sock.sll_family = AF_PACKET; | ||
651 | sock.sll_protocol = htons(ETH_P_IPV6); | ||
652 | sock.sll_ifindex = ifindex; | ||
653 | xbind(fd, (struct sockaddr *) &sock, sizeof(sock)); | ||
654 | |||
655 | #if 0 | ||
656 | if (CLIENT_PORT == 68) { | ||
657 | /* Use only if standard port is in use */ | ||
658 | /* Ignoring error (kernel may lack support for this) */ | ||
659 | if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog, | ||
660 | sizeof(filter_prog)) >= 0) | ||
661 | log1("Attached filter to raw socket fd %d", fd); // log? | ||
662 | } | ||
663 | #endif | ||
664 | |||
665 | log1("Created raw socket"); | ||
666 | |||
667 | return fd; | ||
668 | } | ||
669 | |||
670 | static void change_listen_mode(int new_mode) | ||
671 | { | ||
672 | log1("Entering listen mode: %s", | ||
673 | new_mode != LISTEN_NONE | ||
674 | ? (new_mode == LISTEN_KERNEL ? "kernel" : "raw") | ||
675 | : "none" | ||
676 | ); | ||
677 | |||
678 | listen_mode = new_mode; | ||
679 | if (sockfd >= 0) { | ||
680 | close(sockfd); | ||
681 | sockfd = -1; | ||
682 | } | ||
683 | if (new_mode == LISTEN_KERNEL) | ||
684 | sockfd = udhcp_listen_socket(/*INADDR_ANY,*/ CLIENT_PORT, client_config.interface); | ||
685 | else if (new_mode != LISTEN_NONE) | ||
686 | sockfd = d6_raw_socket(client_config.ifindex); | ||
687 | /* else LISTEN_NONE: sockfd stays closed */ | ||
688 | } | ||
689 | |||
690 | /* Called only on SIGUSR1 */ | ||
691 | static void perform_renew(void) | ||
692 | { | ||
693 | bb_info_msg("Performing a DHCP renew"); | ||
694 | switch (state) { | ||
695 | case BOUND: | ||
696 | change_listen_mode(LISTEN_KERNEL); | ||
697 | case RENEWING: | ||
698 | case REBINDING: | ||
699 | state = RENEW_REQUESTED; | ||
700 | break; | ||
701 | case RENEW_REQUESTED: /* impatient are we? fine, square 1 */ | ||
702 | d6_run_script(NULL, "deconfig"); | ||
703 | case REQUESTING: | ||
704 | case RELEASED: | ||
705 | change_listen_mode(LISTEN_RAW); | ||
706 | state = INIT_SELECTING; | ||
707 | break; | ||
708 | case INIT_SELECTING: | ||
709 | break; | ||
710 | } | ||
711 | } | ||
712 | |||
713 | static void perform_d6_release(struct in6_addr *server_ipv6, struct in6_addr *our_cur_ipv6) | ||
714 | { | ||
715 | /* send release packet */ | ||
716 | if (state == BOUND || state == RENEWING || state == REBINDING) { | ||
717 | bb_info_msg("Unicasting a release"); | ||
718 | send_d6_release(server_ipv6, our_cur_ipv6); /* unicast */ | ||
719 | d6_run_script(NULL, "deconfig"); | ||
720 | } | ||
721 | bb_info_msg("Entering released state"); | ||
722 | |||
723 | change_listen_mode(LISTEN_NONE); | ||
724 | state = RELEASED; | ||
725 | } | ||
726 | |||
727 | ///static uint8_t* alloc_dhcp_option(int code, const char *str, int extra) | ||
728 | ///{ | ||
729 | /// uint8_t *storage; | ||
730 | /// int len = strnlen(str, 255); | ||
731 | /// storage = xzalloc(len + extra + OPT_DATA); | ||
732 | /// storage[OPT_CODE] = code; | ||
733 | /// storage[OPT_LEN] = len + extra; | ||
734 | /// memcpy(storage + extra + OPT_DATA, str, len); | ||
735 | /// return storage; | ||
736 | ///} | ||
737 | |||
738 | #if BB_MMU | ||
739 | static void client_background(void) | ||
740 | { | ||
741 | bb_daemonize(0); | ||
742 | logmode &= ~LOGMODE_STDIO; | ||
743 | /* rewrite pidfile, as our pid is different now */ | ||
744 | write_pidfile(client_config.pidfile); | ||
745 | } | ||
746 | #endif | ||
747 | |||
748 | //usage:#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1 | ||
749 | //usage:# define IF_UDHCP_VERBOSE(...) __VA_ARGS__ | ||
750 | //usage:#else | ||
751 | //usage:# define IF_UDHCP_VERBOSE(...) | ||
752 | //usage:#endif | ||
753 | //usage:#define udhcpc6_trivial_usage | ||
754 | //usage: "[-fbnq"IF_UDHCP_VERBOSE("v")"oRB] [-i IFACE] [-r IP] [-s PROG] [-p PIDFILE]\n" | ||
755 | //usage: " [-x OPT:VAL]... [-O OPT]..." IF_FEATURE_UDHCP_PORT(" [-P N]") | ||
756 | //usage:#define udhcpc6_full_usage "\n" | ||
757 | //usage: IF_LONG_OPTS( | ||
758 | //usage: "\n -i,--interface IFACE Interface to use (default eth0)" | ||
759 | //usage: "\n -p,--pidfile FILE Create pidfile" | ||
760 | //usage: "\n -s,--script PROG Run PROG at DHCP events (default "CONFIG_UDHCPC_DEFAULT_SCRIPT")" | ||
761 | //usage: "\n -B,--broadcast Request broadcast replies" | ||
762 | //usage: "\n -t,--retries N Send up to N discover packets" | ||
763 | //usage: "\n -T,--timeout N Pause between packets (default 3 seconds)" | ||
764 | //usage: "\n -A,--tryagain N Wait N seconds after failure (default 20)" | ||
765 | //usage: "\n -f,--foreground Run in foreground" | ||
766 | //usage: USE_FOR_MMU( | ||
767 | //usage: "\n -b,--background Background if lease is not obtained" | ||
768 | //usage: ) | ||
769 | //usage: "\n -n,--now Exit if lease is not obtained" | ||
770 | //usage: "\n -q,--quit Exit after obtaining lease" | ||
771 | //usage: "\n -R,--release Release IP on exit" | ||
772 | //usage: "\n -S,--syslog Log to syslog too" | ||
773 | //usage: IF_FEATURE_UDHCP_PORT( | ||
774 | //usage: "\n -P,--client-port N Use port N (default 68)" | ||
775 | //usage: ) | ||
776 | ////usage: IF_FEATURE_UDHCPC_ARPING( | ||
777 | ////usage: "\n -a,--arping Use arping to validate offered address" | ||
778 | ////usage: ) | ||
779 | //usage: "\n -O,--request-option OPT Request option OPT from server (cumulative)" | ||
780 | //usage: "\n -o,--no-default-options Don't request any options (unless -O is given)" | ||
781 | //usage: "\n -r,--request IP Request this IP address" | ||
782 | //usage: "\n -x OPT:VAL Include option OPT in sent packets (cumulative)" | ||
783 | //usage: "\n Examples of string, numeric, and hex byte opts:" | ||
784 | //usage: "\n -x hostname:bbox - option 12" | ||
785 | //usage: "\n -x lease:3600 - option 51 (lease time)" | ||
786 | //usage: "\n -x 0x3d:0100BEEFC0FFEE - option 61 (client id)" | ||
787 | //usage: IF_UDHCP_VERBOSE( | ||
788 | //usage: "\n -v Verbose" | ||
789 | //usage: ) | ||
790 | //usage: ) | ||
791 | //usage: IF_NOT_LONG_OPTS( | ||
792 | //usage: "\n -i IFACE Interface to use (default eth0)" | ||
793 | //usage: "\n -p FILE Create pidfile" | ||
794 | //usage: "\n -s PROG Run PROG at DHCP events (default "CONFIG_UDHCPC_DEFAULT_SCRIPT")" | ||
795 | //usage: "\n -B Request broadcast replies" | ||
796 | //usage: "\n -t N Send up to N discover packets" | ||
797 | //usage: "\n -T N Pause between packets (default 3 seconds)" | ||
798 | //usage: "\n -A N Wait N seconds (default 20) after failure" | ||
799 | //usage: "\n -f Run in foreground" | ||
800 | //usage: USE_FOR_MMU( | ||
801 | //usage: "\n -b Background if lease is not obtained" | ||
802 | //usage: ) | ||
803 | //usage: "\n -n Exit if lease is not obtained" | ||
804 | //usage: "\n -q Exit after obtaining lease" | ||
805 | //usage: "\n -R Release IP on exit" | ||
806 | //usage: "\n -S Log to syslog too" | ||
807 | //usage: IF_FEATURE_UDHCP_PORT( | ||
808 | //usage: "\n -P N Use port N (default 68)" | ||
809 | //usage: ) | ||
810 | ////usage: IF_FEATURE_UDHCPC_ARPING( | ||
811 | ////usage: "\n -a Use arping to validate offered address" | ||
812 | ////usage: ) | ||
813 | //usage: "\n -O OPT Request option OPT from server (cumulative)" | ||
814 | //usage: "\n -o Don't request any options (unless -O is given)" | ||
815 | //usage: "\n -r IP Request this IP address" | ||
816 | //usage: "\n -x OPT:VAL Include option OPT in sent packets (cumulative)" | ||
817 | //usage: "\n Examples of string, numeric, and hex byte opts:" | ||
818 | //usage: "\n -x hostname:bbox - option 12" | ||
819 | //usage: "\n -x lease:3600 - option 51 (lease time)" | ||
820 | //usage: "\n -x 0x3d:0100BEEFC0FFEE - option 61 (client id)" | ||
821 | //usage: IF_UDHCP_VERBOSE( | ||
822 | //usage: "\n -v Verbose" | ||
823 | //usage: ) | ||
824 | //usage: ) | ||
825 | //usage: "\nSignals:" | ||
826 | //usage: "\n USR1 Renew lease" | ||
827 | //usage: "\n USR2 Release lease" | ||
828 | |||
829 | |||
830 | int udhcpc6_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
831 | int udhcpc6_main(int argc UNUSED_PARAM, char **argv) | ||
832 | { | ||
833 | const char *str_r; | ||
834 | IF_FEATURE_UDHCP_PORT(char *str_P;) | ||
835 | void *clientid_mac_ptr; | ||
836 | llist_t *list_O = NULL; | ||
837 | llist_t *list_x = NULL; | ||
838 | int tryagain_timeout = 20; | ||
839 | int discover_timeout = 3; | ||
840 | int discover_retries = 3; | ||
841 | struct in6_addr srv6_buf; | ||
842 | struct in6_addr ipv6_buf; | ||
843 | struct in6_addr *requested_ipv6; | ||
844 | uint32_t xid = 0; | ||
845 | int packet_num; | ||
846 | int timeout; /* must be signed */ | ||
847 | unsigned already_waited_sec; | ||
848 | unsigned opt; | ||
849 | int max_fd; | ||
850 | int retval; | ||
851 | fd_set rfds; | ||
852 | |||
853 | /* Default options */ | ||
854 | IF_FEATURE_UDHCP_PORT(SERVER_PORT = 547;) | ||
855 | IF_FEATURE_UDHCP_PORT(CLIENT_PORT = 546;) | ||
856 | client_config.interface = "eth0"; | ||
857 | client_config.script = CONFIG_UDHCPC_DEFAULT_SCRIPT; | ||
858 | |||
859 | /* Parse command line */ | ||
860 | /* O,x: list; -T,-t,-A take numeric param */ | ||
861 | opt_complementary = "O::x::T+:t+:A+" IF_UDHCP_VERBOSE(":vv") ; | ||
862 | IF_LONG_OPTS(applet_long_options = udhcpc_longopts;) | ||
863 | opt = getopt32(argv, "i:np:qRr:s:T:t:SA:O:ox:fB" | ||
864 | USE_FOR_MMU("b") | ||
865 | ///IF_FEATURE_UDHCPC_ARPING("a") | ||
866 | IF_FEATURE_UDHCP_PORT("P:") | ||
867 | "v" | ||
868 | , &client_config.interface, &client_config.pidfile, &str_r /* i,p */ | ||
869 | , &client_config.script /* s */ | ||
870 | , &discover_timeout, &discover_retries, &tryagain_timeout /* T,t,A */ | ||
871 | , &list_O | ||
872 | , &list_x | ||
873 | IF_FEATURE_UDHCP_PORT(, &str_P) | ||
874 | IF_UDHCP_VERBOSE(, &dhcp_verbose) | ||
875 | ); | ||
876 | requested_ipv6 = NULL; | ||
877 | if (opt & OPT_r) { | ||
878 | if (inet_pton(AF_INET6, str_r, &ipv6_buf) <= 0) | ||
879 | bb_error_msg_and_die("bad IPv6 address '%s'", str_r); | ||
880 | requested_ipv6 = &ipv6_buf; | ||
881 | } | ||
882 | #if ENABLE_FEATURE_UDHCP_PORT | ||
883 | if (opt & OPT_P) { | ||
884 | CLIENT_PORT = xatou16(str_P); | ||
885 | SERVER_PORT = CLIENT_PORT - 1; | ||
886 | } | ||
887 | #endif | ||
888 | if (opt & OPT_o) | ||
889 | client_config.no_default_options = 1; | ||
890 | while (list_O) { | ||
891 | char *optstr = llist_pop(&list_O); | ||
892 | unsigned n = bb_strtou(optstr, NULL, 0); | ||
893 | if (errno || n > 254) { | ||
894 | n = udhcp_option_idx(optstr); | ||
895 | n = dhcp_optflags[n].code; | ||
896 | } | ||
897 | client_config.opt_mask[n >> 3] |= 1 << (n & 7); | ||
898 | } | ||
899 | while (list_x) { | ||
900 | char *optstr = llist_pop(&list_x); | ||
901 | char *colon = strchr(optstr, ':'); | ||
902 | if (colon) | ||
903 | *colon = ' '; | ||
904 | /* now it looks similar to udhcpd's config file line: | ||
905 | * "optname optval", using the common routine: */ | ||
906 | udhcp_str2optset(optstr, &client_config.options); | ||
907 | } | ||
908 | |||
909 | if (udhcp_read_interface(client_config.interface, | ||
910 | &client_config.ifindex, | ||
911 | NULL, | ||
912 | client_config.client_mac) | ||
913 | ) { | ||
914 | return 1; | ||
915 | } | ||
916 | |||
917 | /* Create client ID based on mac, set clientid_mac_ptr */ | ||
918 | { | ||
919 | struct d6_option *clientid; | ||
920 | clientid = xzalloc(2+2+2+2+6); | ||
921 | clientid->code = D6_OPT_CLIENTID; | ||
922 | clientid->len = 2+2+6; | ||
923 | clientid->data[0] = 3; /* DUID-LL */ | ||
924 | clientid->data[2] = 1; /* ethernet */ | ||
925 | clientid_mac_ptr = clientid->data + 2+2; | ||
926 | memcpy(clientid_mac_ptr, client_config.client_mac, 6); | ||
927 | client_config.clientid = (void*)clientid; | ||
928 | } | ||
929 | |||
930 | #if !BB_MMU | ||
931 | /* on NOMMU reexec (i.e., background) early */ | ||
932 | if (!(opt & OPT_f)) { | ||
933 | bb_daemonize_or_rexec(0 /* flags */, argv); | ||
934 | logmode = LOGMODE_NONE; | ||
935 | } | ||
936 | #endif | ||
937 | if (opt & OPT_S) { | ||
938 | openlog(applet_name, LOG_PID, LOG_DAEMON); | ||
939 | logmode |= LOGMODE_SYSLOG; | ||
940 | } | ||
941 | |||
942 | /* Make sure fd 0,1,2 are open */ | ||
943 | bb_sanitize_stdio(); | ||
944 | /* Equivalent of doing a fflush after every \n */ | ||
945 | setlinebuf(stdout); | ||
946 | /* Create pidfile */ | ||
947 | write_pidfile(client_config.pidfile); | ||
948 | /* Goes to stdout (unless NOMMU) and possibly syslog */ | ||
949 | bb_info_msg("%s (v"BB_VER") started", applet_name); | ||
950 | /* Set up the signal pipe */ | ||
951 | udhcp_sp_setup(); | ||
952 | /* We want random_xid to be random... */ | ||
953 | srand(monotonic_us()); | ||
954 | |||
955 | state = INIT_SELECTING; | ||
956 | d6_run_script(NULL, "deconfig"); | ||
957 | change_listen_mode(LISTEN_RAW); | ||
958 | packet_num = 0; | ||
959 | timeout = 0; | ||
960 | already_waited_sec = 0; | ||
961 | |||
962 | /* Main event loop. select() waits on signal pipe and possibly | ||
963 | * on sockfd. | ||
964 | * "continue" statements in code below jump to the top of the loop. | ||
965 | */ | ||
966 | for (;;) { | ||
967 | struct timeval tv; | ||
968 | struct d6_packet packet; | ||
969 | uint8_t *packet_end; | ||
970 | /* silence "uninitialized!" warning */ | ||
971 | unsigned timestamp_before_wait = timestamp_before_wait; | ||
972 | |||
973 | //bb_error_msg("sockfd:%d, listen_mode:%d", sockfd, listen_mode); | ||
974 | |||
975 | /* Was opening raw or udp socket here | ||
976 | * if (listen_mode != LISTEN_NONE && sockfd < 0), | ||
977 | * but on fast network renew responses return faster | ||
978 | * than we open sockets. Thus this code is moved | ||
979 | * to change_listen_mode(). Thus we open listen socket | ||
980 | * BEFORE we send renew request (see "case BOUND:"). */ | ||
981 | |||
982 | max_fd = udhcp_sp_fd_set(&rfds, sockfd); | ||
983 | |||
984 | tv.tv_sec = timeout - already_waited_sec; | ||
985 | tv.tv_usec = 0; | ||
986 | retval = 0; | ||
987 | /* If we already timed out, fall through with retval = 0, else... */ | ||
988 | if ((int)tv.tv_sec > 0) { | ||
989 | timestamp_before_wait = (unsigned)monotonic_sec(); | ||
990 | log1("Waiting on select..."); | ||
991 | retval = select(max_fd + 1, &rfds, NULL, NULL, &tv); | ||
992 | if (retval < 0) { | ||
993 | /* EINTR? A signal was caught, don't panic */ | ||
994 | if (errno == EINTR) { | ||
995 | already_waited_sec += (unsigned)monotonic_sec() - timestamp_before_wait; | ||
996 | continue; | ||
997 | } | ||
998 | /* Else: an error occured, panic! */ | ||
999 | bb_perror_msg_and_die("select"); | ||
1000 | } | ||
1001 | } | ||
1002 | |||
1003 | /* If timeout dropped to zero, time to become active: | ||
1004 | * resend discover/renew/whatever | ||
1005 | */ | ||
1006 | if (retval == 0) { | ||
1007 | /* When running on a bridge, the ifindex may have changed | ||
1008 | * (e.g. if member interfaces were added/removed | ||
1009 | * or if the status of the bridge changed). | ||
1010 | * Refresh ifindex and client_mac: | ||
1011 | */ | ||
1012 | if (udhcp_read_interface(client_config.interface, | ||
1013 | &client_config.ifindex, | ||
1014 | NULL, | ||
1015 | client_config.client_mac) | ||
1016 | ) { | ||
1017 | goto ret0; /* iface is gone? */ | ||
1018 | } | ||
1019 | memcpy(clientid_mac_ptr, client_config.client_mac, 6); | ||
1020 | |||
1021 | /* We will restart the wait in any case */ | ||
1022 | already_waited_sec = 0; | ||
1023 | |||
1024 | switch (state) { | ||
1025 | case INIT_SELECTING: | ||
1026 | if (packet_num < discover_retries) { | ||
1027 | if (packet_num == 0) | ||
1028 | xid = random_xid(); | ||
1029 | /* multicast */ | ||
1030 | send_d6_discover(xid, requested_ipv6); | ||
1031 | timeout = discover_timeout; | ||
1032 | packet_num++; | ||
1033 | continue; | ||
1034 | } | ||
1035 | leasefail: | ||
1036 | d6_run_script(NULL, "leasefail"); | ||
1037 | #if BB_MMU /* -b is not supported on NOMMU */ | ||
1038 | if (opt & OPT_b) { /* background if no lease */ | ||
1039 | bb_info_msg("No lease, forking to background"); | ||
1040 | client_background(); | ||
1041 | /* do not background again! */ | ||
1042 | opt = ((opt & ~OPT_b) | OPT_f); | ||
1043 | } else | ||
1044 | #endif | ||
1045 | if (opt & OPT_n) { /* abort if no lease */ | ||
1046 | bb_info_msg("No lease, failing"); | ||
1047 | retval = 1; | ||
1048 | goto ret; | ||
1049 | } | ||
1050 | /* wait before trying again */ | ||
1051 | timeout = tryagain_timeout; | ||
1052 | packet_num = 0; | ||
1053 | continue; | ||
1054 | case REQUESTING: | ||
1055 | if (packet_num < discover_retries) { | ||
1056 | /* send multicast select packet */ | ||
1057 | send_d6_select(xid); | ||
1058 | timeout = discover_timeout; | ||
1059 | packet_num++; | ||
1060 | continue; | ||
1061 | } | ||
1062 | /* Timed out, go back to init state. | ||
1063 | * "discover...select...discover..." loops | ||
1064 | * were seen in the wild. Treat them similarly | ||
1065 | * to "no response to discover" case */ | ||
1066 | change_listen_mode(LISTEN_RAW); | ||
1067 | state = INIT_SELECTING; | ||
1068 | goto leasefail; | ||
1069 | case BOUND: | ||
1070 | /* 1/2 lease passed, enter renewing state */ | ||
1071 | state = RENEWING; | ||
1072 | client_config.first_secs = 0; /* make secs field count from 0 */ | ||
1073 | change_listen_mode(LISTEN_KERNEL); | ||
1074 | log1("Entering renew state"); | ||
1075 | /* fall right through */ | ||
1076 | case RENEW_REQUESTED: /* manual (SIGUSR1) renew */ | ||
1077 | case_RENEW_REQUESTED: | ||
1078 | case RENEWING: | ||
1079 | if (timeout > 60) { | ||
1080 | /* send an unicast renew request */ | ||
1081 | /* Sometimes observed to fail (EADDRNOTAVAIL) to bind | ||
1082 | * a new UDP socket for sending inside send_renew. | ||
1083 | * I hazard to guess existing listening socket | ||
1084 | * is somehow conflicting with it, but why is it | ||
1085 | * not deterministic then?! Strange. | ||
1086 | * Anyway, it does recover by eventually failing through | ||
1087 | * into INIT_SELECTING state. | ||
1088 | */ | ||
1089 | send_d6_renew(xid, &srv6_buf, requested_ipv6); | ||
1090 | timeout >>= 1; | ||
1091 | continue; | ||
1092 | } | ||
1093 | /* Timed out, enter rebinding state */ | ||
1094 | log1("Entering rebinding state"); | ||
1095 | state = REBINDING; | ||
1096 | /* fall right through */ | ||
1097 | case REBINDING: | ||
1098 | /* Switch to bcast receive */ | ||
1099 | change_listen_mode(LISTEN_RAW); | ||
1100 | /* Lease is *really* about to run out, | ||
1101 | * try to find DHCP server using broadcast */ | ||
1102 | if (timeout > 0) { | ||
1103 | /* send a broadcast renew request */ | ||
1104 | send_d6_renew(xid, /*server_ipv6:*/ NULL, requested_ipv6); | ||
1105 | timeout >>= 1; | ||
1106 | continue; | ||
1107 | } | ||
1108 | /* Timed out, enter init state */ | ||
1109 | bb_info_msg("Lease lost, entering init state"); | ||
1110 | d6_run_script(NULL, "deconfig"); | ||
1111 | state = INIT_SELECTING; | ||
1112 | client_config.first_secs = 0; /* make secs field count from 0 */ | ||
1113 | /*timeout = 0; - already is */ | ||
1114 | packet_num = 0; | ||
1115 | continue; | ||
1116 | /* case RELEASED: */ | ||
1117 | } | ||
1118 | /* yah, I know, *you* say it would never happen */ | ||
1119 | timeout = INT_MAX; | ||
1120 | continue; /* back to main loop */ | ||
1121 | } /* if select timed out */ | ||
1122 | |||
1123 | /* select() didn't timeout, something happened */ | ||
1124 | |||
1125 | /* Is it a signal? */ | ||
1126 | /* note: udhcp_sp_read checks FD_ISSET before reading */ | ||
1127 | switch (udhcp_sp_read(&rfds)) { | ||
1128 | case SIGUSR1: | ||
1129 | client_config.first_secs = 0; /* make secs field count from 0 */ | ||
1130 | already_waited_sec = 0; | ||
1131 | perform_renew(); | ||
1132 | if (state == RENEW_REQUESTED) { | ||
1133 | /* We might be either on the same network | ||
1134 | * (in which case renew might work), | ||
1135 | * or we might be on a completely different one | ||
1136 | * (in which case renew won't ever succeed). | ||
1137 | * For the second case, must make sure timeout | ||
1138 | * is not too big, or else we can send | ||
1139 | * futile renew requests for hours. | ||
1140 | * (Ab)use -A TIMEOUT value (usually 20 sec) | ||
1141 | * as a cap on the timeout. | ||
1142 | */ | ||
1143 | if (timeout > tryagain_timeout) | ||
1144 | timeout = tryagain_timeout; | ||
1145 | goto case_RENEW_REQUESTED; | ||
1146 | } | ||
1147 | /* Start things over */ | ||
1148 | packet_num = 0; | ||
1149 | /* Kill any timeouts, user wants this to hurry along */ | ||
1150 | timeout = 0; | ||
1151 | continue; | ||
1152 | case SIGUSR2: | ||
1153 | perform_d6_release(&srv6_buf, requested_ipv6); | ||
1154 | timeout = INT_MAX; | ||
1155 | continue; | ||
1156 | case SIGTERM: | ||
1157 | bb_info_msg("Received SIGTERM"); | ||
1158 | goto ret0; | ||
1159 | } | ||
1160 | |||
1161 | /* Is it a packet? */ | ||
1162 | if (listen_mode == LISTEN_NONE || !FD_ISSET(sockfd, &rfds)) | ||
1163 | continue; /* no */ | ||
1164 | |||
1165 | { | ||
1166 | int len; | ||
1167 | |||
1168 | /* A packet is ready, read it */ | ||
1169 | if (listen_mode == LISTEN_KERNEL) | ||
1170 | len = d6_recv_kernel_packet(&srv6_buf, &packet, sockfd); | ||
1171 | else | ||
1172 | len = d6_recv_raw_packet(&srv6_buf, &packet, sockfd); | ||
1173 | if (len == -1) { | ||
1174 | /* Error is severe, reopen socket */ | ||
1175 | bb_info_msg("Read error: %s, reopening socket", strerror(errno)); | ||
1176 | sleep(discover_timeout); /* 3 seconds by default */ | ||
1177 | change_listen_mode(listen_mode); /* just close and reopen */ | ||
1178 | } | ||
1179 | /* If this packet will turn out to be unrelated/bogus, | ||
1180 | * we will go back and wait for next one. | ||
1181 | * Be sure timeout is properly decreased. */ | ||
1182 | already_waited_sec += (unsigned)monotonic_sec() - timestamp_before_wait; | ||
1183 | if (len < 0) | ||
1184 | continue; | ||
1185 | packet_end = (uint8_t*)&packet + len; | ||
1186 | } | ||
1187 | |||
1188 | if ((packet.d6_xid32 & htonl(0x00ffffff)) != xid) { | ||
1189 | log1("xid %x (our is %x), ignoring packet", | ||
1190 | (unsigned)(packet.d6_xid32 & htonl(0x00ffffff)), (unsigned)xid); | ||
1191 | continue; | ||
1192 | } | ||
1193 | |||
1194 | switch (state) { | ||
1195 | case INIT_SELECTING: | ||
1196 | if (packet.d6_msg_type == D6_MSG_ADVERTISE) | ||
1197 | goto type_is_ok; | ||
1198 | /* DHCPv6 has "Rapid Commit", when instead of Advertise, | ||
1199 | * server sends Reply right away. | ||
1200 | * Fall through to check for this case. | ||
1201 | */ | ||
1202 | case REQUESTING: | ||
1203 | case RENEWING: | ||
1204 | case RENEW_REQUESTED: | ||
1205 | case REBINDING: | ||
1206 | if (packet.d6_msg_type == D6_MSG_REPLY) { | ||
1207 | uint32_t lease_seconds; | ||
1208 | struct d6_option *option, *iaaddr; | ||
1209 | type_is_ok: | ||
1210 | option = d6_find_option(packet.d6_options, packet_end, D6_OPT_STATUS_CODE); | ||
1211 | if (option && option->data[4] != 0) { | ||
1212 | /* return to init state */ | ||
1213 | bb_info_msg("Received DHCP NAK (%u)", option->data[4]); | ||
1214 | d6_run_script(&packet, "nak"); | ||
1215 | if (state != REQUESTING) | ||
1216 | d6_run_script(NULL, "deconfig"); | ||
1217 | change_listen_mode(LISTEN_RAW); | ||
1218 | sleep(3); /* avoid excessive network traffic */ | ||
1219 | state = INIT_SELECTING; | ||
1220 | client_config.first_secs = 0; /* make secs field count from 0 */ | ||
1221 | requested_ipv6 = NULL; | ||
1222 | timeout = 0; | ||
1223 | packet_num = 0; | ||
1224 | already_waited_sec = 0; | ||
1225 | continue; | ||
1226 | } | ||
1227 | option = d6_copy_option(packet.d6_options, packet_end, D6_OPT_SERVERID); | ||
1228 | if (!option) { | ||
1229 | bb_error_msg("no server ID, ignoring packet"); | ||
1230 | continue; | ||
1231 | /* still selecting - this server looks bad */ | ||
1232 | } | ||
1233 | //Note: we do not bother comparing server IDs in Advertise and Reply msgs. | ||
1234 | //server_id variable is used solely for creation of proper server_id option | ||
1235 | //in outgoing packets. (why DHCPv6 even introduced it is a mystery). | ||
1236 | free(client6_data.server_id); | ||
1237 | client6_data.server_id = option; | ||
1238 | if (packet.d6_msg_type == D6_MSG_ADVERTISE) { | ||
1239 | /* enter requesting state */ | ||
1240 | state = REQUESTING; | ||
1241 | timeout = 0; | ||
1242 | packet_num = 0; | ||
1243 | already_waited_sec = 0; | ||
1244 | continue; | ||
1245 | } | ||
1246 | /* It's a D6_MSG_REPLY */ | ||
1247 | /* | ||
1248 | * RFC 3315 18.1.8. Receipt of Reply Messages | ||
1249 | * | ||
1250 | * Upon the receipt of a valid Reply message in response to a Solicit | ||
1251 | * (with a Rapid Commit option), Request, Confirm, Renew, Rebind or | ||
1252 | * Information-request message, the client extracts the configuration | ||
1253 | * information contained in the Reply. The client MAY choose to report | ||
1254 | * any status code or message from the status code option in the Reply | ||
1255 | * message. | ||
1256 | * | ||
1257 | * The client SHOULD perform duplicate address detection [17] on each of | ||
1258 | * the addresses in any IAs it receives in the Reply message before | ||
1259 | * using that address for traffic. If any of the addresses are found to | ||
1260 | * be in use on the link, the client sends a Decline message to the | ||
1261 | * server as described in section 18.1.7. | ||
1262 | * | ||
1263 | * If the Reply was received in response to a Solicit (with a Rapid | ||
1264 | * Commit option), Request, Renew or Rebind message, the client updates | ||
1265 | * the information it has recorded about IAs from the IA options | ||
1266 | * contained in the Reply message: | ||
1267 | * | ||
1268 | * - Record T1 and T2 times. | ||
1269 | * | ||
1270 | * - Add any new addresses in the IA option to the IA as recorded by | ||
1271 | * the client. | ||
1272 | * | ||
1273 | * - Update lifetimes for any addresses in the IA option that the | ||
1274 | * client already has recorded in the IA. | ||
1275 | * | ||
1276 | * - Discard any addresses from the IA, as recorded by the client, that | ||
1277 | * have a valid lifetime of 0 in the IA Address option. | ||
1278 | * | ||
1279 | * - Leave unchanged any information about addresses the client has | ||
1280 | * recorded in the IA but that were not included in the IA from the | ||
1281 | * server. | ||
1282 | * | ||
1283 | * Management of the specific configuration information is detailed in | ||
1284 | * the definition of each option in section 22. | ||
1285 | * | ||
1286 | * If the client receives a Reply message with a Status Code containing | ||
1287 | * UnspecFail, the server is indicating that it was unable to process | ||
1288 | * the message due to an unspecified failure condition. If the client | ||
1289 | * retransmits the original message to the same server to retry the | ||
1290 | * desired operation, the client MUST limit the rate at which it | ||
1291 | * retransmits the message and limit the duration of the time during | ||
1292 | * which it retransmits the message. | ||
1293 | * | ||
1294 | * When the client receives a Reply message with a Status Code option | ||
1295 | * with the value UseMulticast, the client records the receipt of the | ||
1296 | * message and sends subsequent messages to the server through the | ||
1297 | * interface on which the message was received using multicast. The | ||
1298 | * client resends the original message using multicast. | ||
1299 | * | ||
1300 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
1301 | * | OPTION_IA_NA | option-len | | ||
1302 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
1303 | * | IAID (4 octets) | | ||
1304 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
1305 | * | T1 | | ||
1306 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
1307 | * | T2 | | ||
1308 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
1309 | * | | | ||
1310 | * . IA_NA-options . | ||
1311 | * . . | ||
1312 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
1313 | * | ||
1314 | * | ||
1315 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
1316 | * | OPTION_IAADDR | option-len | | ||
1317 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
1318 | * | | | ||
1319 | * | IPv6 address | | ||
1320 | * | | | ||
1321 | * | | | ||
1322 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
1323 | * | preferred-lifetime | | ||
1324 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
1325 | * | valid-lifetime | | ||
1326 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
1327 | * . . | ||
1328 | * . IAaddr-options . | ||
1329 | * . . | ||
1330 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ||
1331 | */ | ||
1332 | free(client6_data.ia_na); | ||
1333 | client6_data.ia_na = d6_copy_option(packet.d6_options, packet_end, D6_OPT_IA_NA); | ||
1334 | if (!client6_data.ia_na) { | ||
1335 | bb_error_msg("no lease time, ignoring packet"); | ||
1336 | continue; | ||
1337 | } | ||
1338 | if (client6_data.ia_na->len < (4 + 4 + 4) + (2 + 2 + 16 + 4 + 4)) { | ||
1339 | bb_error_msg("IA_NA option is too short:%d bytes", client6_data.ia_na->len); | ||
1340 | continue; | ||
1341 | } | ||
1342 | iaaddr = d6_find_option(client6_data.ia_na->data, | ||
1343 | client6_data.ia_na->data + client6_data.ia_na->len, | ||
1344 | D6_OPT_IAADDR | ||
1345 | ); | ||
1346 | if (!iaaddr) { | ||
1347 | bb_error_msg("no lease time, ignoring packet"); | ||
1348 | continue; | ||
1349 | } | ||
1350 | if (iaaddr->len < (16 + 4 + 4)) { | ||
1351 | bb_error_msg("IAADDR option is too short:%d bytes", iaaddr->len); | ||
1352 | continue; | ||
1353 | } | ||
1354 | /* Note: the address is sufficiently aligned for cast: | ||
1355 | * we _copied_ IA-NA, and copy is always well-aligned. | ||
1356 | */ | ||
1357 | requested_ipv6 = (struct in6_addr*) iaaddr->data; | ||
1358 | move_from_unaligned32(lease_seconds, iaaddr->data + 16 + 4); | ||
1359 | lease_seconds = ntohl(lease_seconds); | ||
1360 | /* paranoia: must not be too small and not prone to overflows */ | ||
1361 | if (lease_seconds < 0x10) | ||
1362 | lease_seconds = 0x10; | ||
1363 | /// TODO: check for 0 lease time? | ||
1364 | if (lease_seconds >= 0x10000000) | ||
1365 | lease_seconds = 0x0fffffff; | ||
1366 | /* enter bound state */ | ||
1367 | timeout = lease_seconds / 2; | ||
1368 | bb_info_msg("Lease obtained, lease time %u", | ||
1369 | /*inet_ntoa(temp_addr),*/ (unsigned)lease_seconds); | ||
1370 | d6_run_script(&packet, state == REQUESTING ? "bound" : "renew"); | ||
1371 | |||
1372 | state = BOUND; | ||
1373 | change_listen_mode(LISTEN_NONE); | ||
1374 | if (opt & OPT_q) { /* quit after lease */ | ||
1375 | goto ret0; | ||
1376 | } | ||
1377 | /* future renew failures should not exit (JM) */ | ||
1378 | opt &= ~OPT_n; | ||
1379 | #if BB_MMU /* NOMMU case backgrounded earlier */ | ||
1380 | if (!(opt & OPT_f)) { | ||
1381 | client_background(); | ||
1382 | /* do not background again! */ | ||
1383 | opt = ((opt & ~OPT_b) | OPT_f); | ||
1384 | } | ||
1385 | #endif | ||
1386 | already_waited_sec = 0; | ||
1387 | continue; /* back to main loop */ | ||
1388 | } | ||
1389 | continue; | ||
1390 | /* case BOUND: - ignore all packets */ | ||
1391 | /* case RELEASED: - ignore all packets */ | ||
1392 | } | ||
1393 | /* back to main loop */ | ||
1394 | } /* for (;;) - main loop ends */ | ||
1395 | |||
1396 | ret0: | ||
1397 | if (opt & OPT_R) /* release on quit */ | ||
1398 | perform_d6_release(&srv6_buf, requested_ipv6); | ||
1399 | retval = 0; | ||
1400 | ret: | ||
1401 | /*if (client_config.pidfile) - remove_pidfile has its own check */ | ||
1402 | remove_pidfile(client_config.pidfile); | ||
1403 | return retval; | ||
1404 | } | ||
diff --git a/networking/udhcp/d6_packet.c b/networking/udhcp/d6_packet.c new file mode 100644 index 000000000..3a1bb3df1 --- /dev/null +++ b/networking/udhcp/d6_packet.c | |||
@@ -0,0 +1,168 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * Copyright (C) 2011 Denys Vlasenko. | ||
4 | * | ||
5 | * Licensed under GPLv2, see file LICENSE in this source tree. | ||
6 | */ | ||
7 | #include "common.h" | ||
8 | #include "d6_common.h" | ||
9 | #include "dhcpd.h" | ||
10 | #include <netinet/in.h> | ||
11 | #include <netinet/if_ether.h> | ||
12 | #include <netpacket/packet.h> | ||
13 | |||
14 | #if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 2 | ||
15 | void FAST_FUNC d6_dump_packet(struct d6_packet *packet) | ||
16 | { | ||
17 | if (dhcp_verbose < 2) | ||
18 | return; | ||
19 | |||
20 | bb_info_msg( | ||
21 | " xid %x" | ||
22 | , packet->d6_xid32 | ||
23 | ); | ||
24 | //*bin2hex(buf, (void *) packet->chaddr, sizeof(packet->chaddr)) = '\0'; | ||
25 | //bb_info_msg(" chaddr %s", buf); | ||
26 | } | ||
27 | #endif | ||
28 | |||
29 | int FAST_FUNC d6_recv_kernel_packet(struct in6_addr *peer_ipv6 | ||
30 | UNUSED_PARAM | ||
31 | , struct d6_packet *packet, int fd) | ||
32 | { | ||
33 | int bytes; | ||
34 | |||
35 | memset(packet, 0, sizeof(*packet)); | ||
36 | bytes = safe_read(fd, packet, sizeof(*packet)); | ||
37 | if (bytes < 0) { | ||
38 | log1("Packet read error, ignoring"); | ||
39 | return bytes; /* returns -1 */ | ||
40 | } | ||
41 | |||
42 | if (bytes < offsetof(struct d6_packet, d6_options)) { | ||
43 | bb_info_msg("Packet with bad magic, ignoring"); | ||
44 | return -2; | ||
45 | } | ||
46 | log1("Received a packet"); | ||
47 | d6_dump_packet(packet); | ||
48 | |||
49 | return bytes; | ||
50 | } | ||
51 | |||
52 | /* Construct a ipv6+udp header for a packet, send packet */ | ||
53 | int FAST_FUNC d6_send_raw_packet( | ||
54 | struct d6_packet *d6_pkt, unsigned d6_pkt_size, | ||
55 | struct in6_addr *src_ipv6, int source_port, | ||
56 | struct in6_addr *dst_ipv6, int dest_port, const uint8_t *dest_arp, | ||
57 | int ifindex) | ||
58 | { | ||
59 | struct sockaddr_ll dest_sll; | ||
60 | struct ip6_udp_d6_packet packet; | ||
61 | int fd; | ||
62 | int result = -1; | ||
63 | const char *msg; | ||
64 | |||
65 | fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6)); | ||
66 | if (fd < 0) { | ||
67 | msg = "socket(%s)"; | ||
68 | goto ret_msg; | ||
69 | } | ||
70 | |||
71 | memset(&dest_sll, 0, sizeof(dest_sll)); | ||
72 | memset(&packet, 0, offsetof(struct ip6_udp_d6_packet, data)); | ||
73 | packet.data = *d6_pkt; /* struct copy */ | ||
74 | |||
75 | dest_sll.sll_family = AF_PACKET; | ||
76 | dest_sll.sll_protocol = htons(ETH_P_IPV6); | ||
77 | dest_sll.sll_ifindex = ifindex; | ||
78 | dest_sll.sll_halen = 6; | ||
79 | memcpy(dest_sll.sll_addr, dest_arp, 6); | ||
80 | |||
81 | if (bind(fd, (struct sockaddr *)&dest_sll, sizeof(dest_sll)) < 0) { | ||
82 | msg = "bind(%s)"; | ||
83 | goto ret_close; | ||
84 | } | ||
85 | |||
86 | packet.ip6.ip6_vfc = (6 << 4); /* 4 bits version, top 4 bits of tclass */ | ||
87 | if (src_ipv6) | ||
88 | packet.ip6.ip6_src = *src_ipv6; /* struct copy */ | ||
89 | packet.ip6.ip6_dst = *dst_ipv6; /* struct copy */ | ||
90 | packet.udp.source = htons(source_port); | ||
91 | packet.udp.dest = htons(dest_port); | ||
92 | /* size, excluding IP header: */ | ||
93 | packet.udp.len = htons(sizeof(struct udphdr) + d6_pkt_size); | ||
94 | packet.ip6.ip6_plen = packet.udp.len; | ||
95 | /* UDP checksum skips first four bytes of IP header. | ||
96 | * IPv6 'hop limit' field should be 0. | ||
97 | * 'next header' field should be summed as if it is in a different | ||
98 | * position, therefore we write its value into ip6_hlim: | ||
99 | */ | ||
100 | packet.ip6.ip6_hlim = IPPROTO_UDP; | ||
101 | packet.udp.check = inet_cksum((uint16_t *)&packet + 2, | ||
102 | offsetof(struct ip6_udp_d6_packet, data) - 4 + d6_pkt_size); | ||
103 | /* fix 'hop limit' and 'next header' after UDP checksumming */ | ||
104 | packet.ip6.ip6_hlim = 8; | ||
105 | packet.ip6.ip6_nxt = IPPROTO_UDP; | ||
106 | |||
107 | d6_dump_packet(d6_pkt); | ||
108 | result = sendto(fd, &packet, offsetof(struct ip6_udp_d6_packet, data) + d6_pkt_size, | ||
109 | /*flags:*/ 0, | ||
110 | (struct sockaddr *) &dest_sll, sizeof(dest_sll) | ||
111 | ); | ||
112 | msg = "sendto"; | ||
113 | ret_close: | ||
114 | close(fd); | ||
115 | if (result < 0) { | ||
116 | ret_msg: | ||
117 | bb_perror_msg(msg, "PACKET"); | ||
118 | } | ||
119 | return result; | ||
120 | } | ||
121 | |||
122 | /* Let the kernel do all the work for packet generation */ | ||
123 | int FAST_FUNC d6_send_kernel_packet( | ||
124 | struct d6_packet *d6_pkt, unsigned d6_pkt_size, | ||
125 | struct in6_addr *src_ipv6, int source_port, | ||
126 | struct in6_addr *dst_ipv6, int dest_port) | ||
127 | { | ||
128 | struct sockaddr_in6 sa; | ||
129 | int fd; | ||
130 | int result = -1; | ||
131 | const char *msg; | ||
132 | |||
133 | fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); | ||
134 | if (fd < 0) { | ||
135 | msg = "socket(%s)"; | ||
136 | goto ret_msg; | ||
137 | } | ||
138 | setsockopt_reuseaddr(fd); | ||
139 | |||
140 | memset(&sa, 0, sizeof(sa)); | ||
141 | sa.sin6_family = AF_INET6; | ||
142 | sa.sin6_port = htons(source_port); | ||
143 | sa.sin6_addr = *src_ipv6; /* struct copy */ | ||
144 | if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { | ||
145 | msg = "bind(%s)"; | ||
146 | goto ret_close; | ||
147 | } | ||
148 | |||
149 | memset(&sa, 0, sizeof(sa)); | ||
150 | sa.sin6_family = AF_INET6; | ||
151 | sa.sin6_port = htons(dest_port); | ||
152 | sa.sin6_addr = *dst_ipv6; /* struct copy */ | ||
153 | if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { | ||
154 | msg = "connect"; | ||
155 | goto ret_close; | ||
156 | } | ||
157 | |||
158 | d6_dump_packet(d6_pkt); | ||
159 | result = safe_write(fd, d6_pkt, d6_pkt_size); | ||
160 | msg = "write"; | ||
161 | ret_close: | ||
162 | close(fd); | ||
163 | if (result < 0) { | ||
164 | ret_msg: | ||
165 | bb_perror_msg(msg, "UDP"); | ||
166 | } | ||
167 | return result; | ||
168 | } | ||
diff --git a/networking/udhcp/d6_socket.c b/networking/udhcp/d6_socket.c new file mode 100644 index 000000000..56f69f6a1 --- /dev/null +++ b/networking/udhcp/d6_socket.c | |||
@@ -0,0 +1,34 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * Copyright (C) 2011 Denys Vlasenko. | ||
4 | * | ||
5 | * Licensed under GPLv2, see file LICENSE in this source tree. | ||
6 | */ | ||
7 | #include "common.h" | ||
8 | #include "d6_common.h" | ||
9 | #include <net/if.h> | ||
10 | |||
11 | int FAST_FUNC d6_listen_socket(int port, const char *inf) | ||
12 | { | ||
13 | int fd; | ||
14 | struct sockaddr_in6 addr; | ||
15 | |||
16 | log1("Opening listen socket on *:%d %s", port, inf); | ||
17 | fd = xsocket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); | ||
18 | |||
19 | setsockopt_reuseaddr(fd); | ||
20 | if (setsockopt_broadcast(fd) == -1) | ||
21 | bb_perror_msg_and_die("SO_BROADCAST"); | ||
22 | |||
23 | /* NB: bug 1032 says this doesn't work on ethernet aliases (ethN:M) */ | ||
24 | if (setsockopt_bindtodevice(fd, inf)) | ||
25 | xfunc_die(); /* warning is already printed */ | ||
26 | |||
27 | memset(&addr, 0, sizeof(addr)); | ||
28 | addr.sin6_family = AF_INET6; | ||
29 | addr.sin6_port = htons(port); | ||
30 | /* addr.sin6_addr is all-zeros */ | ||
31 | xbind(fd, (struct sockaddr *)&addr, sizeof(addr)); | ||
32 | |||
33 | return fd; | ||
34 | } | ||