Browse Source

[dhcpv6] Add basic support for stateful and stateless DHCPv6

Add support for the stateful and stateless variants of the DHCPv6
protocol.  The resulting settings block is registered as
"net<x>.dhcpv6", and DHCPv6 options can be obtained using
e.g. "${net0.dhcpv6/23:ipv6}" to obtain the IPv6 DNS server address.

IPv6 addresses obtained via stateful DHCPv6 are not yet applied to the
network device.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
tags/v1.20.1
Michael Brown 11 years ago
parent
commit
2fa34085e2
4 changed files with 1207 additions and 5 deletions
  1. 215
    0
      src/include/ipxe/dhcpv6.h
  2. 1
    0
      src/include/ipxe/errfile.h
  3. 36
    5
      src/net/ndp.c
  4. 955
    0
      src/net/udp/dhcpv6.c

+ 215
- 0
src/include/ipxe/dhcpv6.h View File

@@ -0,0 +1,215 @@
1
+#ifndef _IPXE_DHCPV6_H
2
+#define _IPXE_DHCPV6_H
3
+
4
+/** @file
5
+ *
6
+ * Dynamic Host Configuration Protocol for IPv6
7
+ *
8
+ */
9
+
10
+FILE_LICENCE ( GPL2_OR_LATER );
11
+
12
+#include <stdint.h>
13
+#include <ipxe/in.h>
14
+
15
+/** DHCPv6 server port */
16
+#define DHCPV6_SERVER_PORT 547
17
+
18
+/** DHCPv6 client port */
19
+#define DHCPV6_CLIENT_PORT 546
20
+
21
+/**
22
+ * A DHCPv6 option
23
+ *
24
+ */
25
+struct dhcpv6_option {
26
+	/** Code */
27
+	uint16_t code;
28
+	/** Length of the data field */
29
+	uint16_t len;
30
+	/** Data */
31
+	uint8_t data[0];
32
+} __attribute__ (( packed ));
33
+
34
+/** DHCP unique identifier based on link-layer address (DUID-LL) */
35
+struct dhcpv6_duid_ll {
36
+	/** Type */
37
+	uint16_t type;
38
+	/** Hardware type */
39
+	uint16_t htype;
40
+	/** Link-layer address */
41
+	uint8_t ll_addr[0];
42
+} __attribute__ (( packed ));
43
+
44
+/** DHCP unique identifier based on link-layer address (DUID-LL) */
45
+#define DHCPV6_DUID_LL 3
46
+
47
+/** DHCPv6 client or server identifier option */
48
+struct dhcpv6_duid_option {
49
+	/** Option header */
50
+	struct dhcpv6_option header;
51
+	/** DHCP unique identifier (DUID) */
52
+	uint8_t duid[0];
53
+} __attribute__ (( packed ));
54
+
55
+/** DHCPv6 client identifier option */
56
+#define DHCPV6_CLIENT_ID 1
57
+
58
+/** DHCPv6 server identifier option */
59
+#define DHCPV6_SERVER_ID 2
60
+
61
+/** DHCPv6 identity association for non-temporary address (IA_NA) option */
62
+struct dhcpv6_ia_na_option {
63
+	/** Option header */
64
+	struct dhcpv6_option header;
65
+	/** Identity association identifier (IAID) */
66
+	uint32_t iaid;
67
+	/** Renew time (in seconds) */
68
+	uint32_t renew;
69
+	/** Rebind time (in seconds) */
70
+	uint32_t rebind;
71
+	/** IA_NA options */
72
+	struct dhcpv6_option options[0];
73
+} __attribute__ (( packed ));
74
+
75
+/** DHCPv6 identity association for non-temporary address (IA_NA) option */
76
+#define DHCPV6_IA_NA 3
77
+
78
+/** DHCPv6 identity association address (IAADDR) option */
79
+struct dhcpv6_iaaddr_option {
80
+	/** Option header */
81
+	struct dhcpv6_option header;
82
+	/** IPv6 address */
83
+	struct in6_addr address;
84
+	/** Preferred lifetime (in seconds) */
85
+	uint32_t preferred;
86
+	/** Valid lifetime (in seconds) */
87
+	uint32_t valid;
88
+	/** IAADDR options */
89
+	struct dhcpv6_option options[0];
90
+} __attribute__ (( packed ));
91
+
92
+/** DHCPv6 identity association address (IAADDR) option */
93
+#define DHCPV6_IAADDR 5
94
+
95
+/** DHCPv6 option request option */
96
+struct dhcpv6_option_request_option {
97
+	/** Option header */
98
+	struct dhcpv6_option header;
99
+	/** Requested options */
100
+	uint16_t requested[0];
101
+} __attribute__ (( packed ));
102
+
103
+/** DHCPv6 option request option */
104
+#define DHCPV6_OPTION_REQUEST 6
105
+
106
+/** DHCPv6 elapsed time option */
107
+struct dhcpv6_elapsed_time_option {
108
+	/** Option header */
109
+	struct dhcpv6_option header;
110
+	/** Elapsed time, in centiseconds */
111
+	uint16_t elapsed;
112
+} __attribute__ (( packed ));
113
+
114
+/** DHCPv6 elapsed time option */
115
+#define DHCPV6_ELAPSED_TIME 8
116
+
117
+/** DHCPv6 status code option */
118
+struct dhcpv6_status_code_option {
119
+	/** Option header */
120
+	struct dhcpv6_option header;
121
+	/** Status code */
122
+	uint16_t status;
123
+	/** Status message */
124
+	char message[0];
125
+} __attribute__ (( packed ));
126
+
127
+/** DHCPv6 status code option */
128
+#define DHCPV6_STATUS_CODE 13
129
+
130
+/** DHCPv6 user class */
131
+struct dhcpv6_user_class {
132
+	/** Length */
133
+	uint16_t len;
134
+	/** User class string */
135
+	char string[0];
136
+} __attribute__ (( packed ));
137
+
138
+/** DHCPv6 user class option */
139
+struct dhcpv6_user_class_option {
140
+	/** Option header */
141
+	struct dhcpv6_option header;
142
+	/** User class */
143
+	struct dhcpv6_user_class user_class[0];
144
+} __attribute__ (( packed ));
145
+
146
+/** DHCPv6 user class option */
147
+#define DHCPV6_USER_CLASS 15
148
+
149
+/** DHCPv6 DNS recursive name server option */
150
+#define DHCPV6_DNS_SERVER 23
151
+
152
+/** DHCPv6 domain search list option */
153
+#define DHCPV6_DOMAIN_SEARCH 24
154
+
155
+/**
156
+ * Any DHCPv6 option
157
+ *
158
+ */
159
+union dhcpv6_any_option {
160
+	struct dhcpv6_option header;
161
+	struct dhcpv6_duid_option duid;
162
+	struct dhcpv6_ia_na_option ia_na;
163
+	struct dhcpv6_iaaddr_option iaaddr;
164
+	struct dhcpv6_option_request_option option_request;
165
+	struct dhcpv6_elapsed_time_option elapsed_time;
166
+	struct dhcpv6_status_code_option status_code;
167
+	struct dhcpv6_user_class_option user_class;
168
+};
169
+
170
+/**
171
+ * A DHCPv6 header
172
+ *
173
+ */
174
+struct dhcpv6_header {
175
+	/** Message type */
176
+	uint8_t type;
177
+	/** Transaction ID */
178
+	uint8_t xid[3];
179
+	/** Options */
180
+	struct dhcpv6_option options[0];
181
+} __attribute__ (( packed ));
182
+
183
+/** DHCPv6 solicitation */
184
+#define DHCPV6_SOLICIT 1
185
+
186
+/** DHCPv6 advertisement */
187
+#define DHCPV6_ADVERTISE 2
188
+
189
+/** DHCPv6 request */
190
+#define DHCPV6_REQUEST 3
191
+
192
+/** DHCPv6 reply */
193
+#define DHCPV6_REPLY 7
194
+
195
+/** DHCPv6 information request */
196
+#define DHCPV6_INFORMATION_REQUEST 11
197
+
198
+/** DHCPv6 settings block name */
199
+#define DHCPV6_SETTINGS_NAME "dhcpv6"
200
+
201
+/**
202
+ * Construct all-DHCP-relay-agents-and-servers multicast address
203
+ *
204
+ * @v addr		Zeroed address to construct
205
+ */
206
+static inline void ipv6_all_dhcp_relay_and_servers ( struct in6_addr *addr ) {
207
+	addr->s6_addr16[0] = htons ( 0xff02 );
208
+	addr->s6_addr[13] = 1;
209
+	addr->s6_addr[15] = 2;
210
+}
211
+
212
+extern int start_dhcpv6 ( struct interface *job, struct net_device *netdev,
213
+			  int stateful );
214
+
215
+#endif /* _IPXE_DHCPV6_H */

+ 1
- 0
src/include/ipxe/errfile.h View File

@@ -219,6 +219,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
219 219
 #define ERRFILE_socket			( ERRFILE_NET | 0x00380000 )
220 220
 #define ERRFILE_icmp			( ERRFILE_NET | 0x00390000 )
221 221
 #define ERRFILE_ping			( ERRFILE_NET | 0x003a0000 )
222
+#define ERRFILE_dhcpv6			( ERRFILE_NET | 0x003b0000 )
222 223
 
223 224
 #define ERRFILE_image		      ( ERRFILE_IMAGE | 0x00000000 )
224 225
 #define ERRFILE_elf		      ( ERRFILE_IMAGE | 0x00010000 )

+ 36
- 5
src/net/ndp.c View File

@@ -29,6 +29,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
29 29
 #include <ipxe/ipv6.h>
30 30
 #include <ipxe/icmpv6.h>
31 31
 #include <ipxe/neighbour.h>
32
+#include <ipxe/dhcpv6.h>
32 33
 #include <ipxe/ndp.h>
33 34
 
34 35
 /** @file
@@ -596,6 +597,8 @@ struct ipv6conf {
596 597
 
597 598
 	/** Job control interface */
598 599
 	struct interface job;
600
+	/** DHCPv6 interface */
601
+	struct interface dhcp;
599 602
 
600 603
 	/** Network device being configured */
601 604
 	struct net_device *netdev;
@@ -646,6 +649,7 @@ static void ipv6conf_done ( struct ipv6conf *ipv6conf, int rc ) {
646 649
 
647 650
 	/* Shut down interfaces */
648 651
 	intf_shutdown ( &ipv6conf->job, rc );
652
+	intf_shutdown ( &ipv6conf->dhcp, rc );
649 653
 
650 654
 	/* Stop timer */
651 655
 	stop_timer ( &ipv6conf->timer );
@@ -686,6 +690,8 @@ static void ipv6conf_expired ( struct retry_timer *timer, int fail ) {
686 690
 static int ipv6conf_rx_router_advertisement ( struct net_device *netdev,
687 691
 					      unsigned int flags ) {
688 692
 	struct ipv6conf *ipv6conf;
693
+	int stateful;
694
+	int rc;
689 695
 
690 696
 	/* Identify IPv6 configurator, if any */
691 697
 	ipv6conf = ipv6conf_demux ( netdev );
@@ -697,13 +703,28 @@ static int ipv6conf_rx_router_advertisement ( struct net_device *netdev,
697 703
 		return 0;
698 704
 	}
699 705
 
700
-	/* Fail if stateful address autoconfiguration is required */
701
-	if ( flags & NDP_ROUTER_MANAGED ) {
702
-		ipv6conf_done ( ipv6conf, -ENOTSUP );
703
-		return -ENOTSUP;
706
+	/* If this is not the first solicited router advertisement, ignore it */
707
+	if ( ! timer_running ( &ipv6conf->timer ) )
708
+		return 0;
709
+
710
+	/* Stop router solicitation timer */
711
+	stop_timer ( &ipv6conf->timer );
712
+
713
+	/* Start DHCPv6 if required */
714
+	if ( flags & ( NDP_ROUTER_MANAGED | NDP_ROUTER_OTHER ) ) {
715
+		stateful = ( flags & NDP_ROUTER_MANAGED );
716
+		if ( ( rc = start_dhcpv6 ( &ipv6conf->dhcp, netdev,
717
+					   stateful ) ) != 0 ) {
718
+			DBGC ( netdev, "NDP could not start state%s DHCPv6: "
719
+			       "%s\n", ( stateful ? "ful" : "less" ),
720
+			       strerror ( rc ) );
721
+			ipv6conf_done ( ipv6conf, rc );
722
+			return rc;
723
+		}
724
+		return 0;
704 725
 	}
705 726
 
706
-	/* Mark autoconfiguration as complete */
727
+	/* Otherwise, terminate autoconfiguration */
707 728
 	ipv6conf_done ( ipv6conf, 0 );
708 729
 
709 730
 	return 0;
@@ -718,6 +739,15 @@ static struct interface_operation ipv6conf_job_op[] = {
718 739
 static struct interface_descriptor ipv6conf_job_desc =
719 740
 	INTF_DESC ( struct ipv6conf, job, ipv6conf_job_op );
720 741
 
742
+/** IPv6 configurator DHCPv6 interface operations */
743
+static struct interface_operation ipv6conf_dhcp_op[] = {
744
+	INTF_OP ( intf_close, struct ipv6conf *, ipv6conf_done ),
745
+};
746
+
747
+/** IPv6 configurator DHCPv6 interface descriptor */
748
+static struct interface_descriptor ipv6conf_dhcp_desc =
749
+	INTF_DESC ( struct ipv6conf, dhcp, ipv6conf_dhcp_op );
750
+
721 751
 /**
722 752
  * Start IPv6 autoconfiguration
723 753
  *
@@ -734,6 +764,7 @@ int start_ipv6conf ( struct interface *job, struct net_device *netdev ) {
734 764
 		return -ENOMEM;
735 765
 	ref_init ( &ipv6conf->refcnt, ipv6conf_free );
736 766
 	intf_init ( &ipv6conf->job, &ipv6conf_job_desc, &ipv6conf->refcnt );
767
+	intf_init ( &ipv6conf->dhcp, &ipv6conf_dhcp_desc, &ipv6conf->refcnt );
737 768
 	timer_init ( &ipv6conf->timer, ipv6conf_expired, &ipv6conf->refcnt );
738 769
 	ipv6conf->netdev = netdev_get ( netdev );
739 770
 

+ 955
- 0
src/net/udp/dhcpv6.c View File

@@ -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
+}

Loading…
Cancel
Save