]>
Commit | Line | Data |
---|---|---|
7b143999 TH |
1 | /* |
2 | * SLIRP stateless DHCPv6 | |
3 | * | |
4 | * We only support stateless DHCPv6, e.g. for network booting. | |
5 | * See RFC 3315, RFC 3736, RFC 3646 and RFC 5970 for details. | |
6 | * | |
7 | * Copyright 2016 Thomas Huth, Red Hat Inc. | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2 of the License, | |
12 | * or (at your option) any later version. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License | |
20 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | |
21 | */ | |
22 | ||
7b143999 TH |
23 | #include "slirp.h" |
24 | #include "dhcpv6.h" | |
25 | ||
26 | /* DHCPv6 message types */ | |
27 | #define MSGTYPE_REPLY 7 | |
28 | #define MSGTYPE_INFO_REQUEST 11 | |
29 | ||
30 | /* DHCPv6 option types */ | |
31 | #define OPTION_CLIENTID 1 | |
32 | #define OPTION_IAADDR 5 | |
33 | #define OPTION_ORO 6 | |
34 | #define OPTION_DNS_SERVERS 23 | |
35 | #define OPTION_BOOTFILE_URL 59 | |
36 | ||
37 | struct requested_infos { | |
38 | uint8_t *client_id; | |
39 | int client_id_len; | |
40 | bool want_dns; | |
41 | bool want_boot_url; | |
42 | }; | |
43 | ||
44 | /** | |
45 | * Analyze the info request message sent by the client to see what data it | |
46 | * provided and what it wants to have. The information is gathered in the | |
47 | * "requested_infos" struct. Note that client_id (if provided) points into | |
48 | * the odata region, thus the caller must keep odata valid as long as it | |
49 | * needs to access the requested_infos struct. | |
50 | */ | |
2addc8fb | 51 | static int dhcpv6_parse_info_request(Slirp *slirp, uint8_t *odata, int olen, |
7b143999 TH |
52 | struct requested_infos *ri) |
53 | { | |
54 | int i, req_opt; | |
55 | ||
56 | while (olen > 4) { | |
57 | /* Parse one option */ | |
58 | int option = odata[0] << 8 | odata[1]; | |
59 | int len = odata[2] << 8 | odata[3]; | |
60 | ||
61 | if (len + 4 > olen) { | |
3e0fad3a | 62 | slirp->cb->guest_error("Guest sent bad DHCPv6 packet!", slirp->opaque); |
7b143999 TH |
63 | return -E2BIG; |
64 | } | |
65 | ||
66 | switch (option) { | |
67 | case OPTION_IAADDR: | |
68 | /* According to RFC3315, we must discard requests with IA option */ | |
69 | return -EINVAL; | |
70 | case OPTION_CLIENTID: | |
71 | if (len > 256) { | |
72 | /* Avoid very long IDs which could cause problems later */ | |
73 | return -E2BIG; | |
74 | } | |
75 | ri->client_id = odata + 4; | |
76 | ri->client_id_len = len; | |
77 | break; | |
78 | case OPTION_ORO: /* Option request option */ | |
79 | if (len & 1) { | |
80 | return -EINVAL; | |
81 | } | |
82 | /* Check which options the client wants to have */ | |
83 | for (i = 0; i < len; i += 2) { | |
84 | req_opt = odata[4 + i] << 8 | odata[4 + i + 1]; | |
85 | switch (req_opt) { | |
86 | case OPTION_DNS_SERVERS: | |
87 | ri->want_dns = true; | |
88 | break; | |
89 | case OPTION_BOOTFILE_URL: | |
90 | ri->want_boot_url = true; | |
91 | break; | |
92 | default: | |
226ea7a9 | 93 | DEBUG_MISC("dhcpv6: Unsupported option request %d", |
2afbb788 | 94 | req_opt); |
7b143999 TH |
95 | } |
96 | } | |
97 | break; | |
98 | default: | |
226ea7a9 | 99 | DEBUG_MISC("dhcpv6 info req: Unsupported option %d, len=%d", |
2afbb788 | 100 | option, len); |
7b143999 TH |
101 | } |
102 | ||
103 | odata += len + 4; | |
104 | olen -= len + 4; | |
105 | } | |
106 | ||
107 | return 0; | |
108 | } | |
109 | ||
110 | ||
111 | /** | |
112 | * Handle information request messages | |
113 | */ | |
114 | static void dhcpv6_info_request(Slirp *slirp, struct sockaddr_in6 *srcsas, | |
115 | uint32_t xid, uint8_t *odata, int olen) | |
116 | { | |
117 | struct requested_infos ri = { NULL }; | |
118 | struct sockaddr_in6 sa6, da6; | |
119 | struct mbuf *m; | |
120 | uint8_t *resp; | |
121 | ||
2addc8fb | 122 | if (dhcpv6_parse_info_request(slirp, odata, olen, &ri) < 0) { |
7b143999 TH |
123 | return; |
124 | } | |
125 | ||
126 | m = m_get(slirp); | |
127 | if (!m) { | |
128 | return; | |
129 | } | |
130 | memset(m->m_data, 0, m->m_size); | |
131 | m->m_data += IF_MAXLINKHDR; | |
132 | resp = (uint8_t *)m->m_data + sizeof(struct ip6) + sizeof(struct udphdr); | |
133 | ||
134 | /* Fill in response */ | |
135 | *resp++ = MSGTYPE_REPLY; | |
136 | *resp++ = (uint8_t)(xid >> 16); | |
137 | *resp++ = (uint8_t)(xid >> 8); | |
138 | *resp++ = (uint8_t)xid; | |
139 | ||
140 | if (ri.client_id) { | |
141 | *resp++ = OPTION_CLIENTID >> 8; /* option-code high byte */ | |
142 | *resp++ = OPTION_CLIENTID; /* option-code low byte */ | |
143 | *resp++ = ri.client_id_len >> 8; /* option-len high byte */ | |
144 | *resp++ = ri.client_id_len; /* option-len low byte */ | |
145 | memcpy(resp, ri.client_id, ri.client_id_len); | |
146 | resp += ri.client_id_len; | |
147 | } | |
148 | if (ri.want_dns) { | |
149 | *resp++ = OPTION_DNS_SERVERS >> 8; /* option-code high byte */ | |
150 | *resp++ = OPTION_DNS_SERVERS; /* option-code low byte */ | |
151 | *resp++ = 0; /* option-len high byte */ | |
152 | *resp++ = 16; /* option-len low byte */ | |
153 | memcpy(resp, &slirp->vnameserver_addr6, 16); | |
154 | resp += 16; | |
155 | } | |
156 | if (ri.want_boot_url) { | |
157 | uint8_t *sa = slirp->vhost_addr6.s6_addr; | |
158 | int slen, smaxlen; | |
159 | ||
160 | *resp++ = OPTION_BOOTFILE_URL >> 8; /* option-code high byte */ | |
161 | *resp++ = OPTION_BOOTFILE_URL; /* option-code low byte */ | |
162 | smaxlen = (uint8_t *)m->m_data + IF_MTU - (resp + 2); | |
163 | slen = snprintf((char *)resp + 2, smaxlen, | |
164 | "tftp://[%02x%02x:%02x%02x:%02x%02x:%02x%02x:" | |
165 | "%02x%02x:%02x%02x:%02x%02x:%02x%02x]/%s", | |
166 | sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], sa[6], sa[7], | |
167 | sa[8], sa[9], sa[10], sa[11], sa[12], sa[13], sa[14], | |
168 | sa[15], slirp->bootp_filename); | |
893dcdbf | 169 | slen = MIN(slen, smaxlen); |
7b143999 TH |
170 | *resp++ = slen >> 8; /* option-len high byte */ |
171 | *resp++ = slen; /* option-len low byte */ | |
172 | resp += slen; | |
173 | } | |
174 | ||
175 | sa6.sin6_addr = slirp->vhost_addr6; | |
176 | sa6.sin6_port = DHCPV6_SERVER_PORT; | |
177 | da6.sin6_addr = srcsas->sin6_addr; | |
178 | da6.sin6_port = srcsas->sin6_port; | |
179 | m->m_data += sizeof(struct ip6) + sizeof(struct udphdr); | |
180 | m->m_len = resp - (uint8_t *)m->m_data; | |
181 | udp6_output(NULL, m, &sa6, &da6); | |
182 | } | |
183 | ||
184 | /** | |
185 | * Handle DHCPv6 messages sent by the client | |
186 | */ | |
187 | void dhcpv6_input(struct sockaddr_in6 *srcsas, struct mbuf *m) | |
188 | { | |
189 | uint8_t *data = (uint8_t *)m->m_data + sizeof(struct udphdr); | |
190 | int data_len = m->m_len - sizeof(struct udphdr); | |
191 | uint32_t xid; | |
192 | ||
193 | if (data_len < 4) { | |
194 | return; | |
195 | } | |
196 | ||
197 | xid = ntohl(*(uint32_t *)data) & 0xffffff; | |
198 | ||
199 | switch (data[0]) { | |
200 | case MSGTYPE_INFO_REQUEST: | |
201 | dhcpv6_info_request(m->slirp, srcsas, xid, &data[4], data_len - 4); | |
202 | break; | |
203 | default: | |
226ea7a9 | 204 | DEBUG_MISC("dhcpv6_input: Unsupported message type 0x%x", data[0]); |
7b143999 TH |
205 | } |
206 | } |