package: update version (2.8.0.6)
[sdk/emulator/qemu.git] / slirp / dhcpv6.c
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);
171         slen = min(slen, smaxlen);
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 }