]>
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 | ||
23 | #include "qemu/osdep.h" | |
24 | #include "qemu/log.h" | |
25 | #include "slirp.h" | |
26 | #include "dhcpv6.h" | |
27 | ||
28 | /* DHCPv6 message types */ | |
29 | #define MSGTYPE_REPLY 7 | |
30 | #define MSGTYPE_INFO_REQUEST 11 | |
31 | ||
32 | /* DHCPv6 option types */ | |
33 | #define OPTION_CLIENTID 1 | |
34 | #define OPTION_IAADDR 5 | |
35 | #define OPTION_ORO 6 | |
36 | #define OPTION_DNS_SERVERS 23 | |
37 | #define OPTION_BOOTFILE_URL 59 | |
38 | ||
39 | struct requested_infos { | |
40 | uint8_t *client_id; | |
41 | int client_id_len; | |
42 | bool want_dns; | |
43 | bool want_boot_url; | |
44 | }; | |
45 | ||
46 | /** | |
47 | * Analyze the info request message sent by the client to see what data it | |
48 | * provided and what it wants to have. The information is gathered in the | |
49 | * "requested_infos" struct. Note that client_id (if provided) points into | |
50 | * the odata region, thus the caller must keep odata valid as long as it | |
51 | * needs to access the requested_infos struct. | |
52 | */ | |
53 | static int dhcpv6_parse_info_request(uint8_t *odata, int olen, | |
54 | struct requested_infos *ri) | |
55 | { | |
56 | int i, req_opt; | |
57 | ||
58 | while (olen > 4) { | |
59 | /* Parse one option */ | |
60 | int option = odata[0] << 8 | odata[1]; | |
61 | int len = odata[2] << 8 | odata[3]; | |
62 | ||
63 | if (len + 4 > olen) { | |
64 | qemu_log_mask(LOG_GUEST_ERROR, "Guest sent bad DHCPv6 packet!\n"); | |
65 | return -E2BIG; | |
66 | } | |
67 | ||
68 | switch (option) { | |
69 | case OPTION_IAADDR: | |
70 | /* According to RFC3315, we must discard requests with IA option */ | |
71 | return -EINVAL; | |
72 | case OPTION_CLIENTID: | |
73 | if (len > 256) { | |
74 | /* Avoid very long IDs which could cause problems later */ | |
75 | return -E2BIG; | |
76 | } | |
77 | ri->client_id = odata + 4; | |
78 | ri->client_id_len = len; | |
79 | break; | |
80 | case OPTION_ORO: /* Option request option */ | |
81 | if (len & 1) { | |
82 | return -EINVAL; | |
83 | } | |
84 | /* Check which options the client wants to have */ | |
85 | for (i = 0; i < len; i += 2) { | |
86 | req_opt = odata[4 + i] << 8 | odata[4 + i + 1]; | |
87 | switch (req_opt) { | |
88 | case OPTION_DNS_SERVERS: | |
89 | ri->want_dns = true; | |
90 | break; | |
91 | case OPTION_BOOTFILE_URL: | |
92 | ri->want_boot_url = true; | |
93 | break; | |
94 | default: | |
95 | DEBUG_MISC((dfd, "dhcpv6: Unsupported option request %d\n", | |
96 | req_opt)); | |
97 | } | |
98 | } | |
99 | break; | |
100 | default: | |
101 | DEBUG_MISC((dfd, "dhcpv6 info req: Unsupported option %d, len=%d\n", | |
102 | option, len)); | |
103 | } | |
104 | ||
105 | odata += len + 4; | |
106 | olen -= len + 4; | |
107 | } | |
108 | ||
109 | return 0; | |
110 | } | |
111 | ||
112 | ||
113 | /** | |
114 | * Handle information request messages | |
115 | */ | |
116 | static void dhcpv6_info_request(Slirp *slirp, struct sockaddr_in6 *srcsas, | |
117 | uint32_t xid, uint8_t *odata, int olen) | |
118 | { | |
119 | struct requested_infos ri = { NULL }; | |
120 | struct sockaddr_in6 sa6, da6; | |
121 | struct mbuf *m; | |
122 | uint8_t *resp; | |
123 | ||
124 | if (dhcpv6_parse_info_request(odata, olen, &ri) < 0) { | |
125 | return; | |
126 | } | |
127 | ||
128 | m = m_get(slirp); | |
129 | if (!m) { | |
130 | return; | |
131 | } | |
132 | memset(m->m_data, 0, m->m_size); | |
133 | m->m_data += IF_MAXLINKHDR; | |
134 | resp = (uint8_t *)m->m_data + sizeof(struct ip6) + sizeof(struct udphdr); | |
135 | ||
136 | /* Fill in response */ | |
137 | *resp++ = MSGTYPE_REPLY; | |
138 | *resp++ = (uint8_t)(xid >> 16); | |
139 | *resp++ = (uint8_t)(xid >> 8); | |
140 | *resp++ = (uint8_t)xid; | |
141 | ||
142 | if (ri.client_id) { | |
143 | *resp++ = OPTION_CLIENTID >> 8; /* option-code high byte */ | |
144 | *resp++ = OPTION_CLIENTID; /* option-code low byte */ | |
145 | *resp++ = ri.client_id_len >> 8; /* option-len high byte */ | |
146 | *resp++ = ri.client_id_len; /* option-len low byte */ | |
147 | memcpy(resp, ri.client_id, ri.client_id_len); | |
148 | resp += ri.client_id_len; | |
149 | } | |
150 | if (ri.want_dns) { | |
151 | *resp++ = OPTION_DNS_SERVERS >> 8; /* option-code high byte */ | |
152 | *resp++ = OPTION_DNS_SERVERS; /* option-code low byte */ | |
153 | *resp++ = 0; /* option-len high byte */ | |
154 | *resp++ = 16; /* option-len low byte */ | |
155 | memcpy(resp, &slirp->vnameserver_addr6, 16); | |
156 | resp += 16; | |
157 | } | |
158 | if (ri.want_boot_url) { | |
159 | uint8_t *sa = slirp->vhost_addr6.s6_addr; | |
160 | int slen, smaxlen; | |
161 | ||
162 | *resp++ = OPTION_BOOTFILE_URL >> 8; /* option-code high byte */ | |
163 | *resp++ = OPTION_BOOTFILE_URL; /* option-code low byte */ | |
164 | smaxlen = (uint8_t *)m->m_data + IF_MTU - (resp + 2); | |
165 | slen = snprintf((char *)resp + 2, smaxlen, | |
166 | "tftp://[%02x%02x:%02x%02x:%02x%02x:%02x%02x:" | |
167 | "%02x%02x:%02x%02x:%02x%02x:%02x%02x]/%s", | |
168 | sa[0], sa[1], sa[2], sa[3], sa[4], sa[5], sa[6], sa[7], | |
169 | sa[8], sa[9], sa[10], sa[11], sa[12], sa[13], sa[14], | |
170 | sa[15], slirp->bootp_filename); | |
893dcdbf | 171 | slen = MIN(slen, smaxlen); |
7b143999 TH |
172 | *resp++ = slen >> 8; /* option-len high byte */ |
173 | *resp++ = slen; /* option-len low byte */ | |
174 | resp += slen; | |
175 | } | |
176 | ||
177 | sa6.sin6_addr = slirp->vhost_addr6; | |
178 | sa6.sin6_port = DHCPV6_SERVER_PORT; | |
179 | da6.sin6_addr = srcsas->sin6_addr; | |
180 | da6.sin6_port = srcsas->sin6_port; | |
181 | m->m_data += sizeof(struct ip6) + sizeof(struct udphdr); | |
182 | m->m_len = resp - (uint8_t *)m->m_data; | |
183 | udp6_output(NULL, m, &sa6, &da6); | |
184 | } | |
185 | ||
186 | /** | |
187 | * Handle DHCPv6 messages sent by the client | |
188 | */ | |
189 | void dhcpv6_input(struct sockaddr_in6 *srcsas, struct mbuf *m) | |
190 | { | |
191 | uint8_t *data = (uint8_t *)m->m_data + sizeof(struct udphdr); | |
192 | int data_len = m->m_len - sizeof(struct udphdr); | |
193 | uint32_t xid; | |
194 | ||
195 | if (data_len < 4) { | |
196 | return; | |
197 | } | |
198 | ||
199 | xid = ntohl(*(uint32_t *)data) & 0xffffff; | |
200 | ||
201 | switch (data[0]) { | |
202 | case MSGTYPE_INFO_REQUEST: | |
203 | dhcpv6_info_request(m->slirp, srcsas, xid, &data[4], data_len - 4); | |
204 | break; | |
205 | default: | |
206 | DEBUG_MISC((dfd, "dhcpv6_input: Unsupported message type 0x%x\n", | |
207 | data[0])); | |
208 | } | |
209 | } |