|
@@ -0,0 +1,955 @@
|
|
1
|
+/*
|
|
2
|
+ * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
|
|
3
|
+ *
|
|
4
|
+ * This program is free software; you can redistribute it and/or
|
|
5
|
+ * modify it under the terms of the GNU General Public License as
|
|
6
|
+ * published by the Free Software Foundation; either version 2 of the
|
|
7
|
+ * License, or any later version.
|
|
8
|
+ *
|
|
9
|
+ * This program is distributed in the hope that it will be useful, but
|
|
10
|
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
12
|
+ * General Public License for more details.
|
|
13
|
+ *
|
|
14
|
+ * You should have received a copy of the GNU General Public License
|
|
15
|
+ * along with this program; if not, write to the Free Software
|
|
16
|
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
17
|
+ * 02110-1301, USA.
|
|
18
|
+ */
|
|
19
|
+
|
|
20
|
+FILE_LICENCE ( GPL2_OR_LATER );
|
|
21
|
+
|
|
22
|
+#include <stdlib.h>
|
|
23
|
+#include <stdio.h>
|
|
24
|
+#include <string.h>
|
|
25
|
+#include <errno.h>
|
|
26
|
+#include <byteswap.h>
|
|
27
|
+#include <ipxe/interface.h>
|
|
28
|
+#include <ipxe/xfer.h>
|
|
29
|
+#include <ipxe/iobuf.h>
|
|
30
|
+#include <ipxe/open.h>
|
|
31
|
+#include <ipxe/netdevice.h>
|
|
32
|
+#include <ipxe/settings.h>
|
|
33
|
+#include <ipxe/retry.h>
|
|
34
|
+#include <ipxe/timer.h>
|
|
35
|
+#include <ipxe/in.h>
|
|
36
|
+#include <ipxe/crc32.h>
|
|
37
|
+#include <ipxe/errortab.h>
|
|
38
|
+#include <ipxe/dhcpv6.h>
|
|
39
|
+
|
|
40
|
+/** @file
|
|
41
|
+ *
|
|
42
|
+ * Dynamic Host Configuration Protocol for IPv6
|
|
43
|
+ *
|
|
44
|
+ */
|
|
45
|
+
|
|
46
|
+/* Disambiguate the various error causes */
|
|
47
|
+#define EPROTO_UNSPECFAIL __einfo_error ( EINFO_EPROTO_UNSPECFAIL )
|
|
48
|
+#define EINFO_EPROTO_UNSPECFAIL \
|
|
49
|
+ __einfo_uniqify ( EINFO_EPROTO, 1, "Unspecified server failure" )
|
|
50
|
+#define EPROTO_NOADDRSAVAIL __einfo_error ( EINFO_EPROTO_NOADDRSAVAIL )
|
|
51
|
+#define EINFO_EPROTO_NOADDRSAVAIL \
|
|
52
|
+ __einfo_uniqify ( EINFO_EPROTO, 2, "No addresses available" )
|
|
53
|
+#define EPROTO_NOBINDING __einfo_error ( EINFO_EPROTO_NOBINDING )
|
|
54
|
+#define EINFO_EPROTO_NOBINDING \
|
|
55
|
+ __einfo_uniqify ( EINFO_EPROTO, 3, "Client record unavailable" )
|
|
56
|
+#define EPROTO_NOTONLINK __einfo_error ( EINFO_EPROTO_NOTONLINK )
|
|
57
|
+#define EINFO_EPROTO_NOTONLINK \
|
|
58
|
+ __einfo_uniqify ( EINFO_EPROTO, 4, "Prefix not on link" )
|
|
59
|
+#define EPROTO_USEMULTICAST __einfo_error ( EINFO_EPROTO_USEMULTICAST )
|
|
60
|
+#define EINFO_EPROTO_USEMULTICAST \
|
|
61
|
+ __einfo_uniqify ( EINFO_EPROTO, 5, "Use multicast address" )
|
|
62
|
+#define EPROTO_STATUS( status ) \
|
|
63
|
+ EUNIQ ( EINFO_EPROTO, ( (status) & 0x0f ), EPROTO_UNSPECFAIL, \
|
|
64
|
+ EPROTO_NOADDRSAVAIL, EPROTO_NOBINDING, \
|
|
65
|
+ EPROTO_NOTONLINK, EPROTO_USEMULTICAST )
|
|
66
|
+
|
|
67
|
+/** Human-readable error messages */
|
|
68
|
+struct errortab dhcpv6_errors[] __errortab = {
|
|
69
|
+ __einfo_errortab ( EINFO_EPROTO_NOADDRSAVAIL ),
|
|
70
|
+};
|
|
71
|
+
|
|
72
|
+/****************************************************************************
|
|
73
|
+ *
|
|
74
|
+ * DHCPv6 option lists
|
|
75
|
+ *
|
|
76
|
+ */
|
|
77
|
+
|
|
78
|
+/** A DHCPv6 option list */
|
|
79
|
+struct dhcpv6_option_list {
|
|
80
|
+ /** Data buffer */
|
|
81
|
+ const void *data;
|
|
82
|
+ /** Length of data buffer */
|
|
83
|
+ size_t len;
|
|
84
|
+};
|
|
85
|
+
|
|
86
|
+/**
|
|
87
|
+ * Find DHCPv6 option
|
|
88
|
+ *
|
|
89
|
+ * @v options DHCPv6 option list
|
|
90
|
+ * @v code Option code
|
|
91
|
+ * @ret option DHCPv6 option, or NULL if not found
|
|
92
|
+ */
|
|
93
|
+static const union dhcpv6_any_option *
|
|
94
|
+dhcpv6_option ( struct dhcpv6_option_list *options, unsigned int code ) {
|
|
95
|
+ const union dhcpv6_any_option *option = options->data;
|
|
96
|
+ size_t remaining = options->len;
|
|
97
|
+ size_t data_len;
|
|
98
|
+
|
|
99
|
+ /* Scan through list of options */
|
|
100
|
+ while ( remaining >= sizeof ( option->header ) ) {
|
|
101
|
+
|
|
102
|
+ /* Calculate and validate option length */
|
|
103
|
+ remaining -= sizeof ( option->header );
|
|
104
|
+ data_len = ntohs ( option->header.len );
|
|
105
|
+ if ( data_len > remaining ) {
|
|
106
|
+ /* Malformed option list */
|
|
107
|
+ return NULL;
|
|
108
|
+ }
|
|
109
|
+
|
|
110
|
+ /* Return if we have found the specified option */
|
|
111
|
+ if ( option->header.code == htons ( code ) )
|
|
112
|
+ return option;
|
|
113
|
+
|
|
114
|
+ /* Otherwise, move to the next option */
|
|
115
|
+ option = ( ( ( void * ) option->header.data ) + data_len );
|
|
116
|
+ remaining -= data_len;
|
|
117
|
+ }
|
|
118
|
+
|
|
119
|
+ return NULL;
|
|
120
|
+}
|
|
121
|
+
|
|
122
|
+/**
|
|
123
|
+ * Check DHCPv6 client or server identifier
|
|
124
|
+ *
|
|
125
|
+ * @v options DHCPv6 option list
|
|
126
|
+ * @v code Option code
|
|
127
|
+ * @v expected Expected value
|
|
128
|
+ * @v len Length of expected value
|
|
129
|
+ * @ret rc Return status code
|
|
130
|
+ */
|
|
131
|
+static int dhcpv6_check_duid ( struct dhcpv6_option_list *options,
|
|
132
|
+ unsigned int code, const void *expected,
|
|
133
|
+ size_t len ) {
|
|
134
|
+ const union dhcpv6_any_option *option;
|
|
135
|
+ const struct dhcpv6_duid_option *duid;
|
|
136
|
+
|
|
137
|
+ /* Find option */
|
|
138
|
+ option = dhcpv6_option ( options, code );
|
|
139
|
+ if ( ! option )
|
|
140
|
+ return -ENOENT;
|
|
141
|
+ duid = &option->duid;
|
|
142
|
+
|
|
143
|
+ /* Check option length */
|
|
144
|
+ if ( ntohs ( duid->header.len ) != len )
|
|
145
|
+ return -EINVAL;
|
|
146
|
+
|
|
147
|
+ /* Compare option value */
|
|
148
|
+ if ( memcmp ( duid->duid, expected, len ) != 0 )
|
|
149
|
+ return -EINVAL;
|
|
150
|
+
|
|
151
|
+ return 0;
|
|
152
|
+}
|
|
153
|
+
|
|
154
|
+/**
|
|
155
|
+ * Get DHCPv6 status code
|
|
156
|
+ *
|
|
157
|
+ * @v options DHCPv6 option list
|
|
158
|
+ * @ret rc Return status code
|
|
159
|
+ */
|
|
160
|
+static int dhcpv6_status_code ( struct dhcpv6_option_list *options ) {
|
|
161
|
+ const union dhcpv6_any_option *option;
|
|
162
|
+ const struct dhcpv6_status_code_option *status_code;
|
|
163
|
+ unsigned int status;
|
|
164
|
+
|
|
165
|
+ /* Find status code option, if present */
|
|
166
|
+ option = dhcpv6_option ( options, DHCPV6_STATUS_CODE );
|
|
167
|
+ if ( ! option ) {
|
|
168
|
+ /* Omitted status code should be treated as "success" */
|
|
169
|
+ return 0;
|
|
170
|
+ }
|
|
171
|
+ status_code = &option->status_code;
|
|
172
|
+
|
|
173
|
+ /* Sanity check */
|
|
174
|
+ if ( ntohs ( status_code->header.len ) <
|
|
175
|
+ ( sizeof ( *status_code ) - sizeof ( status_code->header ) ) ) {
|
|
176
|
+ return -EINVAL;
|
|
177
|
+ }
|
|
178
|
+
|
|
179
|
+ /* Calculate iPXE error code from DHCPv6 status code */
|
|
180
|
+ status = ntohs ( status_code->status );
|
|
181
|
+ return ( status ? -EPROTO_STATUS ( status ) : 0 );
|
|
182
|
+}
|
|
183
|
+
|
|
184
|
+/**
|
|
185
|
+ * Get DHCPv6 identity association address
|
|
186
|
+ *
|
|
187
|
+ * @v options DHCPv6 option list
|
|
188
|
+ * @v iaid Identity association ID
|
|
189
|
+ * @v address IPv6 address to fill in
|
|
190
|
+ * @ret rc Return status code
|
|
191
|
+ */
|
|
192
|
+static int dhcpv6_iaaddr ( struct dhcpv6_option_list *options, uint32_t iaid,
|
|
193
|
+ struct in6_addr *address ) {
|
|
194
|
+ const union dhcpv6_any_option *option;
|
|
195
|
+ const struct dhcpv6_ia_na_option *ia_na;
|
|
196
|
+ const struct dhcpv6_iaaddr_option *iaaddr;
|
|
197
|
+ struct dhcpv6_option_list suboptions;
|
|
198
|
+ size_t len;
|
|
199
|
+ int rc;
|
|
200
|
+
|
|
201
|
+ /* Find identity association option, if present */
|
|
202
|
+ option = dhcpv6_option ( options, DHCPV6_IA_NA );
|
|
203
|
+ if ( ! option )
|
|
204
|
+ return -ENOENT;
|
|
205
|
+ ia_na = &option->ia_na;
|
|
206
|
+
|
|
207
|
+ /* Sanity check */
|
|
208
|
+ len = ntohs ( ia_na->header.len );
|
|
209
|
+ if ( len < ( sizeof ( *ia_na ) - sizeof ( ia_na->header ) ) )
|
|
210
|
+ return -EINVAL;
|
|
211
|
+
|
|
212
|
+ /* Check identity association ID */
|
|
213
|
+ if ( ia_na->iaid != htonl ( iaid ) )
|
|
214
|
+ return -EINVAL;
|
|
215
|
+
|
|
216
|
+ /* Construct IA_NA sub-options list */
|
|
217
|
+ suboptions.data = ia_na->options;
|
|
218
|
+ suboptions.len = ( len + sizeof ( ia_na->header ) -
|
|
219
|
+ offsetof ( typeof ( *ia_na ), options ) );
|
|
220
|
+
|
|
221
|
+ /* Check IA_NA status code */
|
|
222
|
+ if ( ( rc = dhcpv6_status_code ( &suboptions ) ) != 0 )
|
|
223
|
+ return rc;
|
|
224
|
+
|
|
225
|
+ /* Find identity association address, if present */
|
|
226
|
+ option = dhcpv6_option ( &suboptions, DHCPV6_IAADDR );
|
|
227
|
+ if ( ! option )
|
|
228
|
+ return -ENOENT;
|
|
229
|
+ iaaddr = &option->iaaddr;
|
|
230
|
+
|
|
231
|
+ /* Sanity check */
|
|
232
|
+ len = ntohs ( iaaddr->header.len );
|
|
233
|
+ if ( len < ( sizeof ( *iaaddr ) - sizeof ( iaaddr->header ) ) )
|
|
234
|
+ return -EINVAL;
|
|
235
|
+
|
|
236
|
+ /* Construct IAADDR sub-options list */
|
|
237
|
+ suboptions.data = iaaddr->options;
|
|
238
|
+ suboptions.len = ( len + sizeof ( iaaddr->header ) -
|
|
239
|
+ offsetof ( typeof ( *iaaddr ), options ) );
|
|
240
|
+
|
|
241
|
+ /* Check IAADDR status code */
|
|
242
|
+ if ( ( rc = dhcpv6_status_code ( &suboptions ) ) != 0 )
|
|
243
|
+ return rc;
|
|
244
|
+
|
|
245
|
+ /* Extract IPv6 address */
|
|
246
|
+ memcpy ( address, &iaaddr->address, sizeof ( *address ) );
|
|
247
|
+
|
|
248
|
+ return 0;
|
|
249
|
+}
|
|
250
|
+
|
|
251
|
+/****************************************************************************
|
|
252
|
+ *
|
|
253
|
+ * DHCPv6 settings blocks
|
|
254
|
+ *
|
|
255
|
+ */
|
|
256
|
+
|
|
257
|
+/** DHCPv6 settings scope */
|
|
258
|
+static struct settings_scope dhcpv6_settings_scope;
|
|
259
|
+
|
|
260
|
+/** A DHCPv6 settings block */
|
|
261
|
+struct dhcpv6_settings {
|
|
262
|
+ /** Reference count */
|
|
263
|
+ struct refcnt refcnt;
|
|
264
|
+ /** Settings block */
|
|
265
|
+ struct settings settings;
|
|
266
|
+ /** Option list */
|
|
267
|
+ struct dhcpv6_option_list options;
|
|
268
|
+};
|
|
269
|
+
|
|
270
|
+/**
|
|
271
|
+ * Check applicability of DHCPv6 setting
|
|
272
|
+ *
|
|
273
|
+ * @v settings Settings block
|
|
274
|
+ * @v setting Setting
|
|
275
|
+ * @ret applies Setting applies within this settings block
|
|
276
|
+ */
|
|
277
|
+static int dhcpv6_applies ( struct settings *settings __unused,
|
|
278
|
+ struct setting *setting ) {
|
|
279
|
+
|
|
280
|
+ return ( setting->scope == &dhcpv6_settings_scope );
|
|
281
|
+}
|
|
282
|
+
|
|
283
|
+/**
|
|
284
|
+ * Fetch value of DHCPv6 setting
|
|
285
|
+ *
|
|
286
|
+ * @v settings Settings block
|
|
287
|
+ * @v setting Setting to fetch
|
|
288
|
+ * @v data Buffer to fill with setting data
|
|
289
|
+ * @v len Length of buffer
|
|
290
|
+ * @ret len Length of setting data, or negative error
|
|
291
|
+ */
|
|
292
|
+static int dhcpv6_fetch ( struct settings *settings,
|
|
293
|
+ struct setting *setting,
|
|
294
|
+ void *data, size_t len ) {
|
|
295
|
+ struct dhcpv6_settings *dhcpv6set =
|
|
296
|
+ container_of ( settings, struct dhcpv6_settings, settings );
|
|
297
|
+ const union dhcpv6_any_option *option;
|
|
298
|
+ size_t option_len;
|
|
299
|
+
|
|
300
|
+ /* Find option */
|
|
301
|
+ option = dhcpv6_option ( &dhcpv6set->options, setting->tag );
|
|
302
|
+ if ( ! option )
|
|
303
|
+ return -ENOENT;
|
|
304
|
+
|
|
305
|
+ /* Copy option to data buffer */
|
|
306
|
+ option_len = ntohs ( option->header.len );
|
|
307
|
+ if ( len > option_len )
|
|
308
|
+ len = option_len;
|
|
309
|
+ memcpy ( data, option->header.data, len );
|
|
310
|
+ return option_len;
|
|
311
|
+}
|
|
312
|
+
|
|
313
|
+/** DHCPv6 settings operations */
|
|
314
|
+static struct settings_operations dhcpv6_settings_operations = {
|
|
315
|
+ .applies = dhcpv6_applies,
|
|
316
|
+ .fetch = dhcpv6_fetch,
|
|
317
|
+};
|
|
318
|
+
|
|
319
|
+/**
|
|
320
|
+ * Register DHCPv6 options as network device settings
|
|
321
|
+ *
|
|
322
|
+ * @v options DHCPv6 option list
|
|
323
|
+ * @v parent Parent settings block
|
|
324
|
+ * @ret rc Return status code
|
|
325
|
+ */
|
|
326
|
+static int dhcpv6_register ( struct dhcpv6_option_list *options,
|
|
327
|
+ struct settings *parent ) {
|
|
328
|
+ struct dhcpv6_settings *dhcpv6set;
|
|
329
|
+ void *data;
|
|
330
|
+ size_t len;
|
|
331
|
+ int rc;
|
|
332
|
+
|
|
333
|
+ /* Allocate and initialise structure */
|
|
334
|
+ dhcpv6set = zalloc ( sizeof ( *dhcpv6set ) + options->len );
|
|
335
|
+ if ( ! dhcpv6set ) {
|
|
336
|
+ rc = -ENOMEM;
|
|
337
|
+ goto err_alloc;
|
|
338
|
+ }
|
|
339
|
+ ref_init ( &dhcpv6set->refcnt, NULL );
|
|
340
|
+ settings_init ( &dhcpv6set->settings, &dhcpv6_settings_operations,
|
|
341
|
+ &dhcpv6set->refcnt, &dhcpv6_settings_scope );
|
|
342
|
+ data = ( ( ( void * ) dhcpv6set ) + sizeof ( *dhcpv6set ) );
|
|
343
|
+ len = options->len;
|
|
344
|
+ memcpy ( data, options->data, len );
|
|
345
|
+ dhcpv6set->options.data = data;
|
|
346
|
+ dhcpv6set->options.len = len;
|
|
347
|
+
|
|
348
|
+ /* Register settings */
|
|
349
|
+ if ( ( rc = register_settings ( &dhcpv6set->settings, parent,
|
|
350
|
+ DHCPV6_SETTINGS_NAME ) ) != 0 )
|
|
351
|
+ goto err_register;
|
|
352
|
+
|
|
353
|
+ err_register:
|
|
354
|
+ ref_put ( &dhcpv6set->refcnt );
|
|
355
|
+ err_alloc:
|
|
356
|
+ return rc;
|
|
357
|
+}
|
|
358
|
+
|
|
359
|
+/****************************************************************************
|
|
360
|
+ *
|
|
361
|
+ * DHCPv6 protocol
|
|
362
|
+ *
|
|
363
|
+ */
|
|
364
|
+
|
|
365
|
+/** Options to be requested */
|
|
366
|
+static uint16_t dhcpv6_requested_options[] = {
|
|
367
|
+ htons ( DHCPV6_DNS_SERVER ), htons ( DHCPV6_DOMAIN_SEARCH ),
|
|
368
|
+};
|
|
369
|
+
|
|
370
|
+/**
|
|
371
|
+ * Name a DHCPv6 packet type
|
|
372
|
+ *
|
|
373
|
+ * @v type DHCPv6 packet type
|
|
374
|
+ * @ret name DHCPv6 packet type name
|
|
375
|
+ */
|
|
376
|
+static __attribute__ (( unused )) const char *
|
|
377
|
+dhcpv6_type_name ( unsigned int type ) {
|
|
378
|
+ static char buf[ 12 /* "UNKNOWN-xxx" + NUL */ ];
|
|
379
|
+
|
|
380
|
+ switch ( type ) {
|
|
381
|
+ case DHCPV6_SOLICIT: return "SOLICIT";
|
|
382
|
+ case DHCPV6_ADVERTISE: return "ADVERTISE";
|
|
383
|
+ case DHCPV6_REQUEST: return "REQUEST";
|
|
384
|
+ case DHCPV6_REPLY: return "REPLY";
|
|
385
|
+ case DHCPV6_INFORMATION_REQUEST: return "INFORMATION-REQUEST";
|
|
386
|
+ default:
|
|
387
|
+ snprintf ( buf, sizeof ( buf ), "UNKNOWN-%d", type );
|
|
388
|
+ return buf;
|
|
389
|
+ }
|
|
390
|
+}
|
|
391
|
+
|
|
392
|
+/** A DHCPv6 session state */
|
|
393
|
+struct dhcpv6_session_state {
|
|
394
|
+ /** Current transmitted packet type */
|
|
395
|
+ uint8_t tx_type;
|
|
396
|
+ /** Current expected received packet type */
|
|
397
|
+ uint8_t rx_type;
|
|
398
|
+ /** Flags */
|
|
399
|
+ uint8_t flags;
|
|
400
|
+ /** Next state (or NULL to terminate) */
|
|
401
|
+ struct dhcpv6_session_state *next;
|
|
402
|
+};
|
|
403
|
+
|
|
404
|
+/** DHCPv6 session state flags */
|
|
405
|
+enum dhcpv6_session_state_flags {
|
|
406
|
+ /** Include identity association within request */
|
|
407
|
+ DHCPV6_TX_IA_NA = 0x01,
|
|
408
|
+ /** Include leased IPv6 address within request */
|
|
409
|
+ DHCPV6_TX_IAADDR = 0x02,
|
|
410
|
+ /** Record received server ID */
|
|
411
|
+ DHCPV6_RX_SERVER_ID = 0x04,
|
|
412
|
+ /** Record received IPv6 address */
|
|
413
|
+ DHCPV6_RX_IAADDR = 0x08,
|
|
414
|
+};
|
|
415
|
+
|
|
416
|
+/** DHCPv6 request state */
|
|
417
|
+static struct dhcpv6_session_state dhcpv6_request = {
|
|
418
|
+ .tx_type = DHCPV6_REQUEST,
|
|
419
|
+ .rx_type = DHCPV6_REPLY,
|
|
420
|
+ .flags = ( DHCPV6_TX_IA_NA | DHCPV6_TX_IAADDR | DHCPV6_RX_IAADDR ),
|
|
421
|
+ .next = NULL,
|
|
422
|
+};
|
|
423
|
+
|
|
424
|
+/** DHCPv6 solicitation state */
|
|
425
|
+static struct dhcpv6_session_state dhcpv6_solicit = {
|
|
426
|
+ .tx_type = DHCPV6_SOLICIT,
|
|
427
|
+ .rx_type = DHCPV6_ADVERTISE,
|
|
428
|
+ .flags = ( DHCPV6_TX_IA_NA | DHCPV6_RX_SERVER_ID | DHCPV6_RX_IAADDR ),
|
|
429
|
+ .next = &dhcpv6_request,
|
|
430
|
+};
|
|
431
|
+
|
|
432
|
+/** DHCPv6 information request state */
|
|
433
|
+static struct dhcpv6_session_state dhcpv6_information_request = {
|
|
434
|
+ .tx_type = DHCPV6_INFORMATION_REQUEST,
|
|
435
|
+ .rx_type = DHCPV6_REPLY,
|
|
436
|
+ .flags = 0,
|
|
437
|
+ .next = NULL,
|
|
438
|
+};
|
|
439
|
+
|
|
440
|
+/** A DHCPv6 session */
|
|
441
|
+struct dhcpv6_session {
|
|
442
|
+ /** Reference counter */
|
|
443
|
+ struct refcnt refcnt;
|
|
444
|
+ /** Job control interface */
|
|
445
|
+ struct interface job;
|
|
446
|
+ /** Data transfer interface */
|
|
447
|
+ struct interface xfer;
|
|
448
|
+
|
|
449
|
+ /** Network device being configured */
|
|
450
|
+ struct net_device *netdev;
|
|
451
|
+ /** Transaction ID */
|
|
452
|
+ uint8_t xid[3];
|
|
453
|
+ /** Identity association ID */
|
|
454
|
+ uint32_t iaid;
|
|
455
|
+ /** Start time (in ticks) */
|
|
456
|
+ unsigned long start;
|
|
457
|
+ /** Client DUID */
|
|
458
|
+ void *client_duid;
|
|
459
|
+ /** Client DUID length */
|
|
460
|
+ size_t client_duid_len;
|
|
461
|
+ /** Server DUID, if known */
|
|
462
|
+ void *server_duid;
|
|
463
|
+ /** Server DUID length */
|
|
464
|
+ size_t server_duid_len;
|
|
465
|
+ /** Leased IPv6 address */
|
|
466
|
+ struct in6_addr lease;
|
|
467
|
+
|
|
468
|
+ /** Retransmission timer */
|
|
469
|
+ struct retry_timer timer;
|
|
470
|
+
|
|
471
|
+ /** Current session state */
|
|
472
|
+ struct dhcpv6_session_state *state;
|
|
473
|
+ /** Current timeout status code */
|
|
474
|
+ int rc;
|
|
475
|
+};
|
|
476
|
+
|
|
477
|
+/**
|
|
478
|
+ * Free DHCPv6 session
|
|
479
|
+ *
|
|
480
|
+ * @v refcnt Reference count
|
|
481
|
+ */
|
|
482
|
+static void dhcpv6_free ( struct refcnt *refcnt ) {
|
|
483
|
+ struct dhcpv6_session *dhcpv6 =
|
|
484
|
+ container_of ( refcnt, struct dhcpv6_session, refcnt );
|
|
485
|
+
|
|
486
|
+ netdev_put ( dhcpv6->netdev );
|
|
487
|
+ free ( dhcpv6->server_duid );
|
|
488
|
+ free ( dhcpv6 );
|
|
489
|
+}
|
|
490
|
+
|
|
491
|
+/**
|
|
492
|
+ * Terminate DHCPv6 session
|
|
493
|
+ *
|
|
494
|
+ * @v dhcpv6 DHCPv6 session
|
|
495
|
+ * @v rc Reason for close
|
|
496
|
+ */
|
|
497
|
+static void dhcpv6_finished ( struct dhcpv6_session *dhcpv6, int rc ) {
|
|
498
|
+
|
|
499
|
+ /* Stop timer */
|
|
500
|
+ stop_timer ( &dhcpv6->timer );
|
|
501
|
+
|
|
502
|
+ /* Shut down interfaces */
|
|
503
|
+ intf_shutdown ( &dhcpv6->xfer, rc );
|
|
504
|
+ intf_shutdown ( &dhcpv6->job, rc );
|
|
505
|
+}
|
|
506
|
+
|
|
507
|
+/**
|
|
508
|
+ * Transition to new DHCPv6 session state
|
|
509
|
+ *
|
|
510
|
+ * @v dhcpv6 DHCPv6 session
|
|
511
|
+ * @v state New session state
|
|
512
|
+ */
|
|
513
|
+static void dhcpv6_set_state ( struct dhcpv6_session *dhcpv6,
|
|
514
|
+ struct dhcpv6_session_state *state ) {
|
|
515
|
+
|
|
516
|
+ DBGC ( dhcpv6, "DHCPv6 %s entering %s state\n", dhcpv6->netdev->name,
|
|
517
|
+ dhcpv6_type_name ( state->tx_type ) );
|
|
518
|
+
|
|
519
|
+ /* Record state */
|
|
520
|
+ dhcpv6->state = state;
|
|
521
|
+
|
|
522
|
+ /* Default to -ETIMEDOUT if no more specific error is recorded */
|
|
523
|
+ dhcpv6->rc = -ETIMEDOUT;
|
|
524
|
+
|
|
525
|
+ /* Start timer to trigger transmission */
|
|
526
|
+ start_timer_nodelay ( &dhcpv6->timer );
|
|
527
|
+}
|
|
528
|
+
|
|
529
|
+/**
|
|
530
|
+ * Get DHCPv6 user class
|
|
531
|
+ *
|
|
532
|
+ * @v data Data buffer
|
|
533
|
+ * @v len Length of data buffer
|
|
534
|
+ * @ret len Length of user class
|
|
535
|
+ */
|
|
536
|
+static size_t dhcpv6_user_class ( void *data, size_t len ) {
|
|
537
|
+ static const char default_user_class[4] = { 'i', 'P', 'X', 'E' };
|
|
538
|
+ int actual_len;
|
|
539
|
+
|
|
540
|
+ /* Fetch user-class setting, if defined */
|
|
541
|
+ actual_len = fetch_setting ( NULL, &user_class_setting, data, len );
|
|
542
|
+ if ( actual_len >= 0 )
|
|
543
|
+ return actual_len;
|
|
544
|
+
|
|
545
|
+ /* Otherwise, use the default user class ("iPXE") */
|
|
546
|
+ if ( len > sizeof ( default_user_class ) )
|
|
547
|
+ len = sizeof ( default_user_class );
|
|
548
|
+ memcpy ( data, default_user_class, len );
|
|
549
|
+ return sizeof ( default_user_class );
|
|
550
|
+}
|
|
551
|
+
|
|
552
|
+/**
|
|
553
|
+ * Transmit current request
|
|
554
|
+ *
|
|
555
|
+ * @v dhcpv6 DHCPv6 session
|
|
556
|
+ * @ret rc Return status code
|
|
557
|
+ */
|
|
558
|
+static int dhcpv6_tx ( struct dhcpv6_session *dhcpv6 ) {
|
|
559
|
+ struct dhcpv6_duid_option *client_id;
|
|
560
|
+ struct dhcpv6_duid_option *server_id;
|
|
561
|
+ struct dhcpv6_ia_na_option *ia_na;
|
|
562
|
+ struct dhcpv6_iaaddr_option *iaaddr;
|
|
563
|
+ struct dhcpv6_option_request_option *option_request;
|
|
564
|
+ struct dhcpv6_user_class_option *user_class;
|
|
565
|
+ struct dhcpv6_elapsed_time_option *elapsed;
|
|
566
|
+ struct dhcpv6_header *dhcphdr;
|
|
567
|
+ struct io_buffer *iobuf;
|
|
568
|
+ size_t client_id_len;
|
|
569
|
+ size_t server_id_len;
|
|
570
|
+ size_t ia_na_len;
|
|
571
|
+ size_t option_request_len;
|
|
572
|
+ size_t user_class_string_len;
|
|
573
|
+ size_t user_class_len;
|
|
574
|
+ size_t elapsed_len;
|
|
575
|
+ size_t total_len;
|
|
576
|
+ int rc;
|
|
577
|
+
|
|
578
|
+ /* Calculate lengths */
|
|
579
|
+ client_id_len = ( sizeof ( *client_id ) + dhcpv6->client_duid_len );
|
|
580
|
+ server_id_len = ( dhcpv6->server_duid ? ( sizeof ( *server_id ) +
|
|
581
|
+ dhcpv6->server_duid_len ) :0);
|
|
582
|
+ if ( dhcpv6->state->flags & DHCPV6_TX_IA_NA ) {
|
|
583
|
+ ia_na_len = sizeof ( *ia_na );
|
|
584
|
+ if ( dhcpv6->state->flags & DHCPV6_TX_IAADDR )
|
|
585
|
+ ia_na_len += sizeof ( *iaaddr );
|
|
586
|
+ } else {
|
|
587
|
+ ia_na_len = 0;
|
|
588
|
+ }
|
|
589
|
+ option_request_len = ( sizeof ( *option_request ) +
|
|
590
|
+ sizeof ( dhcpv6_requested_options ) );
|
|
591
|
+ user_class_string_len = dhcpv6_user_class ( NULL, 0 );
|
|
592
|
+ user_class_len = ( sizeof ( *user_class ) +
|
|
593
|
+ sizeof ( user_class->user_class[0] ) +
|
|
594
|
+ user_class_string_len );
|
|
595
|
+ elapsed_len = sizeof ( *elapsed );
|
|
596
|
+ total_len = ( sizeof ( *dhcphdr ) + client_id_len + server_id_len +
|
|
597
|
+ ia_na_len + option_request_len + user_class_len +
|
|
598
|
+ elapsed_len );
|
|
599
|
+
|
|
600
|
+ /* Allocate packet */
|
|
601
|
+ iobuf = xfer_alloc_iob ( &dhcpv6->xfer, total_len );
|
|
602
|
+ if ( ! iobuf )
|
|
603
|
+ return -ENOMEM;
|
|
604
|
+
|
|
605
|
+ /* Construct header */
|
|
606
|
+ dhcphdr = iob_put ( iobuf, sizeof ( *dhcphdr ) );
|
|
607
|
+ dhcphdr->type = dhcpv6->state->tx_type;
|
|
608
|
+ memcpy ( dhcphdr->xid, dhcpv6->xid, sizeof ( dhcphdr->xid ) );
|
|
609
|
+
|
|
610
|
+ /* Construct client identifier */
|
|
611
|
+ client_id = iob_put ( iobuf, client_id_len );
|
|
612
|
+ client_id->header.code = htons ( DHCPV6_CLIENT_ID );
|
|
613
|
+ client_id->header.len = htons ( client_id_len -
|
|
614
|
+ sizeof ( client_id->header ) );
|
|
615
|
+ memcpy ( client_id->duid, dhcpv6->client_duid,
|
|
616
|
+ dhcpv6->client_duid_len );
|
|
617
|
+
|
|
618
|
+ /* Construct server identifier, if applicable */
|
|
619
|
+ if ( server_id_len ) {
|
|
620
|
+ server_id = iob_put ( iobuf, server_id_len );
|
|
621
|
+ server_id->header.code = htons ( DHCPV6_SERVER_ID );
|
|
622
|
+ server_id->header.len = htons ( server_id_len -
|
|
623
|
+ sizeof ( server_id->header ) );
|
|
624
|
+ memcpy ( server_id->duid, dhcpv6->server_duid,
|
|
625
|
+ dhcpv6->server_duid_len );
|
|
626
|
+ }
|
|
627
|
+
|
|
628
|
+ /* Construct identity association, if applicable */
|
|
629
|
+ if ( ia_na_len ) {
|
|
630
|
+ ia_na = iob_put ( iobuf, ia_na_len );
|
|
631
|
+ ia_na->header.code = htons ( DHCPV6_IA_NA );
|
|
632
|
+ ia_na->header.len = htons ( ia_na_len -
|
|
633
|
+ sizeof ( ia_na->header ) );
|
|
634
|
+ ia_na->iaid = htonl ( dhcpv6->iaid );
|
|
635
|
+ ia_na->renew = htonl ( 0 );
|
|
636
|
+ ia_na->rebind = htonl ( 0 );
|
|
637
|
+ if ( dhcpv6->state->flags & DHCPV6_TX_IAADDR ) {
|
|
638
|
+ iaaddr = ( ( void * ) ia_na->options );
|
|
639
|
+ iaaddr->header.code = htons ( DHCPV6_IAADDR );
|
|
640
|
+ iaaddr->header.len = htons ( sizeof ( *iaaddr ) -
|
|
641
|
+ sizeof ( iaaddr->header ));
|
|
642
|
+ memcpy ( &iaaddr->address, &dhcpv6->lease,
|
|
643
|
+ sizeof ( iaaddr->address ) );
|
|
644
|
+ iaaddr->preferred = htonl ( 0 );
|
|
645
|
+ iaaddr->valid = htonl ( 0 );
|
|
646
|
+ }
|
|
647
|
+ }
|
|
648
|
+
|
|
649
|
+ /* Construct option request */
|
|
650
|
+ option_request = iob_put ( iobuf, option_request_len );
|
|
651
|
+ option_request->header.code = htons ( DHCPV6_OPTION_REQUEST );
|
|
652
|
+ option_request->header.len = htons ( option_request_len -
|
|
653
|
+ sizeof ( option_request->header ));
|
|
654
|
+ memcpy ( option_request->requested, dhcpv6_requested_options,
|
|
655
|
+ sizeof ( dhcpv6_requested_options ) );
|
|
656
|
+
|
|
657
|
+ /* Construct user class */
|
|
658
|
+ user_class = iob_put ( iobuf, user_class_len );
|
|
659
|
+ user_class->header.code = htons ( DHCPV6_USER_CLASS );
|
|
660
|
+ user_class->header.len = htons ( user_class_len -
|
|
661
|
+ sizeof ( user_class->header ) );
|
|
662
|
+ user_class->user_class[0].len = htons ( user_class_string_len );
|
|
663
|
+ dhcpv6_user_class ( user_class->user_class[0].string,
|
|
664
|
+ user_class_string_len );
|
|
665
|
+
|
|
666
|
+ /* Construct elapsed time */
|
|
667
|
+ elapsed = iob_put ( iobuf, elapsed_len );
|
|
668
|
+ elapsed->header.code = htons ( DHCPV6_ELAPSED_TIME );
|
|
669
|
+ elapsed->header.len = htons ( elapsed_len -
|
|
670
|
+ sizeof ( elapsed->header ) );
|
|
671
|
+ elapsed->elapsed = htons ( ( ( currticks() - dhcpv6->start ) * 100 ) /
|
|
672
|
+ TICKS_PER_SEC );
|
|
673
|
+
|
|
674
|
+ /* Sanity check */
|
|
675
|
+ assert ( iob_len ( iobuf ) == total_len );
|
|
676
|
+
|
|
677
|
+ /* Transmit packet */
|
|
678
|
+ if ( ( rc = xfer_deliver_iob ( &dhcpv6->xfer, iobuf ) ) != 0 ) {
|
|
679
|
+ DBGC ( dhcpv6, "DHCPv6 %s could not transmit: %s\n",
|
|
680
|
+ dhcpv6->netdev->name, strerror ( rc ) );
|
|
681
|
+ return rc;
|
|
682
|
+ }
|
|
683
|
+
|
|
684
|
+ return 0;
|
|
685
|
+}
|
|
686
|
+
|
|
687
|
+/**
|
|
688
|
+ * Handle timer expiry
|
|
689
|
+ *
|
|
690
|
+ * @v timer Retransmission timer
|
|
691
|
+ * @v fail Failure indicator
|
|
692
|
+ */
|
|
693
|
+static void dhcpv6_timer_expired ( struct retry_timer *timer, int fail ) {
|
|
694
|
+ struct dhcpv6_session *dhcpv6 =
|
|
695
|
+ container_of ( timer, struct dhcpv6_session, timer );
|
|
696
|
+
|
|
697
|
+ /* If we have failed, terminate DHCPv6 */
|
|
698
|
+ if ( fail ) {
|
|
699
|
+ dhcpv6_finished ( dhcpv6, dhcpv6->rc );
|
|
700
|
+ return;
|
|
701
|
+ }
|
|
702
|
+
|
|
703
|
+ /* Restart timer */
|
|
704
|
+ start_timer ( &dhcpv6->timer );
|
|
705
|
+
|
|
706
|
+ /* (Re)transmit current request */
|
|
707
|
+ dhcpv6_tx ( dhcpv6 );
|
|
708
|
+}
|
|
709
|
+
|
|
710
|
+/**
|
|
711
|
+ * Receive new data
|
|
712
|
+ *
|
|
713
|
+ * @v dhcpv6 DHCPv6 session
|
|
714
|
+ * @v iobuf I/O buffer
|
|
715
|
+ * @v meta Data transfer metadata
|
|
716
|
+ * @ret rc Return status code
|
|
717
|
+ */
|
|
718
|
+static int dhcpv6_rx ( struct dhcpv6_session *dhcpv6,
|
|
719
|
+ struct io_buffer *iobuf,
|
|
720
|
+ struct xfer_metadata *meta ) {
|
|
721
|
+ struct settings *parent = netdev_settings ( dhcpv6->netdev );
|
|
722
|
+ struct sockaddr_in6 *src = ( ( struct sockaddr_in6 * ) meta->src );
|
|
723
|
+ struct dhcpv6_header *dhcphdr = iobuf->data;
|
|
724
|
+ struct dhcpv6_option_list options;
|
|
725
|
+ const union dhcpv6_any_option *option;
|
|
726
|
+ int rc;
|
|
727
|
+
|
|
728
|
+ /* Sanity checks */
|
|
729
|
+ if ( iob_len ( iobuf ) < sizeof ( *dhcphdr ) ) {
|
|
730
|
+ DBGC ( dhcpv6, "DHCPv6 %s received packet too short (%zd "
|
|
731
|
+ "bytes, min %zd bytes)\n", dhcpv6->netdev->name,
|
|
732
|
+ iob_len ( iobuf ), sizeof ( *dhcphdr ) );
|
|
733
|
+ rc = -EINVAL;
|
|
734
|
+ goto done;
|
|
735
|
+ }
|
|
736
|
+ assert ( src != NULL );
|
|
737
|
+ assert ( src->sin6_family == AF_INET6 );
|
|
738
|
+ DBGC ( dhcpv6, "DHCPv6 %s received %s from %s\n",
|
|
739
|
+ dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ),
|
|
740
|
+ inet6_ntoa ( &src->sin6_addr ) );
|
|
741
|
+
|
|
742
|
+ /* Construct option list */
|
|
743
|
+ options.data = dhcphdr->options;
|
|
744
|
+ options.len = ( iob_len ( iobuf ) -
|
|
745
|
+ offsetof ( typeof ( *dhcphdr ), options ) );
|
|
746
|
+
|
|
747
|
+ /* Verify client identifier */
|
|
748
|
+ if ( ( rc = dhcpv6_check_duid ( &options, DHCPV6_CLIENT_ID,
|
|
749
|
+ dhcpv6->client_duid,
|
|
750
|
+ dhcpv6->client_duid_len ) ) != 0 ) {
|
|
751
|
+ DBGC ( dhcpv6, "DHCPv6 %s received %s without correct client "
|
|
752
|
+ "ID: %s\n", dhcpv6->netdev->name,
|
|
753
|
+ dhcpv6_type_name ( dhcphdr->type ), strerror ( rc ) );
|
|
754
|
+ goto done;
|
|
755
|
+ }
|
|
756
|
+
|
|
757
|
+ /* Verify server identifier, if applicable */
|
|
758
|
+ if ( dhcpv6->server_duid &&
|
|
759
|
+ ( ( rc = dhcpv6_check_duid ( &options, DHCPV6_SERVER_ID,
|
|
760
|
+ dhcpv6->server_duid,
|
|
761
|
+ dhcpv6->server_duid_len ) ) != 0 ) ) {
|
|
762
|
+ DBGC ( dhcpv6, "DHCPv6 %s received %s without correct server "
|
|
763
|
+ "ID: %s\n", dhcpv6->netdev->name,
|
|
764
|
+ dhcpv6_type_name ( dhcphdr->type ), strerror ( rc ) );
|
|
765
|
+ goto done;
|
|
766
|
+ }
|
|
767
|
+
|
|
768
|
+ /* Check message type */
|
|
769
|
+ if ( dhcphdr->type != dhcpv6->state->rx_type ) {
|
|
770
|
+ DBGC ( dhcpv6, "DHCPv6 %s received %s while expecting %s\n",
|
|
771
|
+ dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ),
|
|
772
|
+ dhcpv6_type_name ( dhcpv6->state->rx_type ) );
|
|
773
|
+ rc = -ENOTTY;
|
|
774
|
+ goto done;
|
|
775
|
+ }
|
|
776
|
+
|
|
777
|
+ /* Fetch status code, if present */
|
|
778
|
+ if ( ( rc = dhcpv6_status_code ( &options ) ) != 0 ) {
|
|
779
|
+ DBGC ( dhcpv6, "DHCPv6 %s received %s with error status: %s\n",
|
|
780
|
+ dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ),
|
|
781
|
+ strerror ( rc ) );
|
|
782
|
+ /* This is plausibly the error we want to return */
|
|
783
|
+ dhcpv6->rc = rc;
|
|
784
|
+ goto done;
|
|
785
|
+ }
|
|
786
|
+
|
|
787
|
+ /* Record identity association address, if applicable */
|
|
788
|
+ if ( dhcpv6->state->flags & DHCPV6_RX_IAADDR ) {
|
|
789
|
+ if ( ( rc = dhcpv6_iaaddr ( &options, dhcpv6->iaid,
|
|
790
|
+ &dhcpv6->lease ) ) != 0 ) {
|
|
791
|
+ DBGC ( dhcpv6, "DHCPv6 %s received %s with unusable "
|
|
792
|
+ "IAADDR: %s\n", dhcpv6->netdev->name,
|
|
793
|
+ dhcpv6_type_name ( dhcphdr->type ),
|
|
794
|
+ strerror ( rc ) );
|
|
795
|
+ /* This is plausibly the error we want to return */
|
|
796
|
+ dhcpv6->rc = rc;
|
|
797
|
+ goto done;
|
|
798
|
+ }
|
|
799
|
+ DBGC ( dhcpv6, "DHCPv6 %s received %s is for %s\n",
|
|
800
|
+ dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ),
|
|
801
|
+ inet6_ntoa ( &dhcpv6->lease ) );
|
|
802
|
+ }
|
|
803
|
+
|
|
804
|
+ /* Record server ID, if applicable */
|
|
805
|
+ if ( dhcpv6->state->flags & DHCPV6_RX_SERVER_ID ) {
|
|
806
|
+ assert ( dhcpv6->server_duid == NULL );
|
|
807
|
+ option = dhcpv6_option ( &options, DHCPV6_SERVER_ID );
|
|
808
|
+ if ( ! option ) {
|
|
809
|
+ DBGC ( dhcpv6, "DHCPv6 %s received %s missing server "
|
|
810
|
+ "ID\n", dhcpv6->netdev->name,
|
|
811
|
+ dhcpv6_type_name ( dhcphdr->type ) );
|
|
812
|
+ rc = -EINVAL;
|
|
813
|
+ goto done;
|
|
814
|
+ }
|
|
815
|
+ dhcpv6->server_duid_len = ntohs ( option->duid.header.len );
|
|
816
|
+ dhcpv6->server_duid = malloc ( dhcpv6->server_duid_len );
|
|
817
|
+ if ( ! dhcpv6->server_duid ) {
|
|
818
|
+ rc = -ENOMEM;
|
|
819
|
+ goto done;
|
|
820
|
+ }
|
|
821
|
+ memcpy ( dhcpv6->server_duid, option->duid.duid,
|
|
822
|
+ dhcpv6->server_duid_len );
|
|
823
|
+ }
|
|
824
|
+
|
|
825
|
+ /* Transition to next state or complete DHCPv6, as applicable */
|
|
826
|
+ if ( dhcpv6->state->next ) {
|
|
827
|
+
|
|
828
|
+ /* Transition to next state */
|
|
829
|
+ dhcpv6_set_state ( dhcpv6, dhcpv6->state->next );
|
|
830
|
+ rc = 0;
|
|
831
|
+
|
|
832
|
+ } else {
|
|
833
|
+
|
|
834
|
+ /* Register settings */
|
|
835
|
+ if ( ( rc = dhcpv6_register ( &options, parent ) ) != 0 ) {
|
|
836
|
+ DBGC ( dhcpv6, "DHCPv6 %s could not register "
|
|
837
|
+ "settings: %s\n", dhcpv6->netdev->name,
|
|
838
|
+ strerror ( rc ) );
|
|
839
|
+ goto done;
|
|
840
|
+ }
|
|
841
|
+
|
|
842
|
+ /* Mark as complete */
|
|
843
|
+ dhcpv6_finished ( dhcpv6, 0 );
|
|
844
|
+ DBGC ( dhcpv6, "DHCPv6 %s complete\n", dhcpv6->netdev->name );
|
|
845
|
+ }
|
|
846
|
+
|
|
847
|
+ done:
|
|
848
|
+ free_iob ( iobuf );
|
|
849
|
+ return rc;
|
|
850
|
+}
|
|
851
|
+
|
|
852
|
+/** DHCPv6 job control interface operations */
|
|
853
|
+static struct interface_operation dhcpv6_job_op[] = {
|
|
854
|
+ INTF_OP ( intf_close, struct dhcpv6_session *, dhcpv6_finished ),
|
|
855
|
+};
|
|
856
|
+
|
|
857
|
+/** DHCPv6 job control interface descriptor */
|
|
858
|
+static struct interface_descriptor dhcpv6_job_desc =
|
|
859
|
+ INTF_DESC ( struct dhcpv6_session, job, dhcpv6_job_op );
|
|
860
|
+
|
|
861
|
+/** DHCPv6 data transfer interface operations */
|
|
862
|
+static struct interface_operation dhcpv6_xfer_op[] = {
|
|
863
|
+ INTF_OP ( xfer_deliver, struct dhcpv6_session *, dhcpv6_rx ),
|
|
864
|
+};
|
|
865
|
+
|
|
866
|
+/** DHCPv6 data transfer interface descriptor */
|
|
867
|
+static struct interface_descriptor dhcpv6_xfer_desc =
|
|
868
|
+ INTF_DESC ( struct dhcpv6_session, xfer, dhcpv6_xfer_op );
|
|
869
|
+
|
|
870
|
+/**
|
|
871
|
+ * Start DHCPv6
|
|
872
|
+ *
|
|
873
|
+ * @v job Job control interface
|
|
874
|
+ * @v netdev Network device
|
|
875
|
+ * @v stateful Perform stateful address autoconfiguration
|
|
876
|
+ * @ret rc Return status code
|
|
877
|
+ */
|
|
878
|
+int start_dhcpv6 ( struct interface *job, struct net_device *netdev,
|
|
879
|
+ int stateful ) {
|
|
880
|
+ struct ll_protocol *ll_protocol = netdev->ll_protocol;
|
|
881
|
+ struct dhcpv6_session *dhcpv6;
|
|
882
|
+ struct {
|
|
883
|
+ union {
|
|
884
|
+ struct sockaddr_in6 sin6;
|
|
885
|
+ struct sockaddr sa;
|
|
886
|
+ } client;
|
|
887
|
+ union {
|
|
888
|
+ struct sockaddr_in6 sin6;
|
|
889
|
+ struct sockaddr sa;
|
|
890
|
+ } server;
|
|
891
|
+ } addresses;
|
|
892
|
+ struct dhcpv6_duid_ll *client_duid;
|
|
893
|
+ size_t client_duid_len;
|
|
894
|
+ uint32_t xid;
|
|
895
|
+ int rc;
|
|
896
|
+
|
|
897
|
+ /* Allocate and initialise structure */
|
|
898
|
+ client_duid_len = ( sizeof ( *client_duid ) + ll_protocol->ll_addr_len);
|
|
899
|
+ dhcpv6 = zalloc ( sizeof ( *dhcpv6 ) + client_duid_len );
|
|
900
|
+ if ( ! dhcpv6 )
|
|
901
|
+ return -ENOMEM;
|
|
902
|
+ ref_init ( &dhcpv6->refcnt, dhcpv6_free );
|
|
903
|
+ intf_init ( &dhcpv6->job, &dhcpv6_job_desc, &dhcpv6->refcnt );
|
|
904
|
+ intf_init ( &dhcpv6->xfer, &dhcpv6_xfer_desc, &dhcpv6->refcnt );
|
|
905
|
+ dhcpv6->netdev = netdev_get ( netdev );
|
|
906
|
+ xid = random();
|
|
907
|
+ memcpy ( dhcpv6->xid, &xid, sizeof ( dhcpv6->xid ) );
|
|
908
|
+ dhcpv6->start = currticks();
|
|
909
|
+ dhcpv6->client_duid = ( ( ( void * ) dhcpv6 ) + sizeof ( *dhcpv6 ) );
|
|
910
|
+ dhcpv6->client_duid_len = client_duid_len;
|
|
911
|
+ timer_init ( &dhcpv6->timer, dhcpv6_timer_expired, &dhcpv6->refcnt );
|
|
912
|
+
|
|
913
|
+ /* Construct client and server addresses */
|
|
914
|
+ memset ( &addresses, 0, sizeof ( addresses ) );
|
|
915
|
+ addresses.client.sin6.sin6_family = AF_INET6;
|
|
916
|
+ addresses.client.sin6.sin6_scope_id = netdev->index;
|
|
917
|
+ addresses.client.sin6.sin6_port = htons ( DHCPV6_CLIENT_PORT );
|
|
918
|
+ addresses.server.sin6.sin6_family = AF_INET6;
|
|
919
|
+ ipv6_all_dhcp_relay_and_servers ( &addresses.server.sin6.sin6_addr );
|
|
920
|
+ addresses.server.sin6.sin6_scope_id = netdev->index;
|
|
921
|
+ addresses.server.sin6.sin6_port = htons ( DHCPV6_SERVER_PORT );
|
|
922
|
+
|
|
923
|
+ /* Construct client DUID and IAID from link-layer address */
|
|
924
|
+ client_duid = dhcpv6->client_duid;
|
|
925
|
+ client_duid->type = htons ( DHCPV6_DUID_LL );
|
|
926
|
+ client_duid->htype = ll_protocol->ll_proto;
|
|
927
|
+ memcpy ( client_duid->ll_addr, netdev->ll_addr,
|
|
928
|
+ ll_protocol->ll_addr_len );
|
|
929
|
+ dhcpv6->iaid = crc32_le ( 0, netdev->ll_addr, ll_protocol->ll_addr_len);
|
|
930
|
+ DBGC ( dhcpv6, "DHCPv6 %s has XID %02x%02x%02x\n", dhcpv6->netdev->name,
|
|
931
|
+ dhcpv6->xid[0], dhcpv6->xid[1], dhcpv6->xid[2] );
|
|
932
|
+
|
|
933
|
+ /* Enter initial state */
|
|
934
|
+ dhcpv6_set_state ( dhcpv6, ( stateful ? &dhcpv6_solicit :
|
|
935
|
+ &dhcpv6_information_request ) );
|
|
936
|
+
|
|
937
|
+ /* Open socket */
|
|
938
|
+ if ( ( rc = xfer_open_socket ( &dhcpv6->xfer, SOCK_DGRAM,
|
|
939
|
+ &addresses.server.sa,
|
|
940
|
+ &addresses.client.sa ) ) != 0 ) {
|
|
941
|
+ DBGC ( dhcpv6, "DHCPv6 %s could not open socket: %s\n",
|
|
942
|
+ dhcpv6->netdev->name, strerror ( rc ) );
|
|
943
|
+ goto err_open_socket;
|
|
944
|
+ }
|
|
945
|
+
|
|
946
|
+ /* Attach parent interface, mortalise self, and return */
|
|
947
|
+ intf_plug_plug ( &dhcpv6->job, job );
|
|
948
|
+ ref_put ( &dhcpv6->refcnt );
|
|
949
|
+ return 0;
|
|
950
|
+
|
|
951
|
+ err_open_socket:
|
|
952
|
+ dhcpv6_finished ( dhcpv6, rc );
|
|
953
|
+ ref_put ( &dhcpv6->refcnt );
|
|
954
|
+ return rc;
|
|
955
|
+}
|