Add server ID option support to dhcp-lib
[platform/upstream/connman.git] / gdhcp / common.c
1 /*
2  *  DHCP library with GLib integration
3  *
4  *  Copyright (C) 2007-2010  Intel Corporation. All rights reserved.
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License version 2 as
8  *  published by the Free Software Foundation.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <errno.h>
26 #include <unistd.h>
27 #include <stdint.h>
28 #include <string.h>
29 #include <endian.h>
30 #include <netpacket/packet.h>
31 #include <net/ethernet.h>
32
33 #include "gdhcp.h"
34 #include "common.h"
35
36 static const DHCPOption client_options[] = {
37         { OPTION_IP,                    0x01 }, /* subnet-mask */
38         { OPTION_IP | OPTION_LIST,      0x03 }, /* routers */
39         { OPTION_IP | OPTION_LIST,      0x06 }, /* domain-name-servers */
40         { OPTION_STRING,                0x0c }, /* hostname */
41         { OPTION_STRING,                0x0f }, /* domain-name */
42         { OPTION_IP | OPTION_LIST,      0x2a }, /* ntp-servers */
43         { OPTION_U32,                   0x33 }, /* dhcp-lease-time */
44         /* Options below will not be exposed to user */
45         { OPTION_IP,                    0x32 }, /* requested-ip */
46         { OPTION_U8,                    0x35 }, /* message-type */
47         { OPTION_U32,                   0x36 }, /* server-id */
48         { OPTION_U16,                   0x39 }, /* max-size */
49         { OPTION_STRING,                0x3c }, /* vendor */
50         { OPTION_STRING,                0x3d }, /* client-id */
51         { OPTION_UNKNOWN,               0x00 },
52 };
53
54 GDHCPOptionType dhcp_get_code_type(uint8_t code)
55 {
56         int i;
57
58         for (i = 0; client_options[i].code; i++) {
59                 if (client_options[i].code == code)
60                         return client_options[i].type;
61         }
62
63         return OPTION_UNKNOWN;
64 }
65
66 uint8_t *dhcp_get_option(struct dhcp_packet *packet, int code)
67 {
68         int len, rem;
69         uint8_t *optionptr;
70         uint8_t overload = 0;
71
72         /* option bytes: [code][len][data1][data2]..[dataLEN] */
73         optionptr = packet->options;
74         rem = sizeof(packet->options);
75
76         while (1) {
77                 if (rem <= 0)
78                         /* Bad packet, malformed option field */
79                         return NULL;
80
81                 if (optionptr[OPT_CODE] == DHCP_PADDING) {
82                         rem--;
83                         optionptr++;
84
85                         continue;
86                 }
87
88                 if (optionptr[OPT_CODE] == DHCP_END) {
89                         if (overload & FILE_FIELD) {
90                                 overload &= ~FILE_FIELD;
91
92                                 optionptr = packet->file;
93                                 rem = sizeof(packet->file);
94
95                                 continue;
96                         } else if (overload & SNAME_FIELD) {
97                                 overload &= ~SNAME_FIELD;
98
99                                 optionptr = packet->sname;
100                                 rem = sizeof(packet->sname);
101
102                                 continue;
103                         }
104
105                         break;
106                 }
107
108                 len = 2 + optionptr[OPT_LEN];
109
110                 rem -= len;
111                 if (rem < 0)
112                         continue; /* complain and return NULL */
113
114                 if (optionptr[OPT_CODE] == code)
115                         return optionptr + OPT_DATA;
116
117                 if (optionptr[OPT_CODE] == DHCP_OPTION_OVERLOAD)
118                         overload |= optionptr[OPT_DATA];
119
120                 optionptr += len;
121         }
122
123         return NULL;
124 }
125
126 int dhcp_end_option(uint8_t *optionptr)
127 {
128         int i = 0;
129
130         while (optionptr[i] != DHCP_END) {
131                 if (optionptr[i] != DHCP_PADDING)
132                         i += optionptr[i + OPT_LEN] + OPT_DATA - 1;
133
134                 i++;
135         }
136
137         return i;
138 }
139
140 /*
141  * Add an option (supplied in binary form) to the options.
142  * Option format: [code][len][data1][data2]..[dataLEN]
143  */
144 void dhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt)
145 {
146         unsigned len;
147         uint8_t *optionptr = packet->options;
148         unsigned end = dhcp_end_option(optionptr);
149
150         len = OPT_DATA + addopt[OPT_LEN];
151
152         /* end position + (option code/length + addopt length) + end option */
153         if (end + len + 1 >= DHCP_OPTIONS_BUFSIZE)
154                 /* option did not fit into the packet */
155                 return;
156
157         memcpy(optionptr + end, addopt, len);
158
159         optionptr[end + len] = DHCP_END;
160 }
161
162 void dhcp_add_simple_option(struct dhcp_packet *packet, uint8_t code,
163                                                         uint32_t data)
164 {
165         uint8_t option[6], len;
166         GDHCPOptionType type = dhcp_get_code_type(code);
167
168         if (type == OPTION_UNKNOWN)
169                 return;
170
171         option[OPT_CODE] = code;
172
173         len = dhcp_option_lengths[type & OPTION_TYPE_MASK];
174         option[OPT_LEN] = len;
175
176 #if __BYTE_ORDER == __BIG_ENDIAN
177         data <<= 8 * (4 - len);
178 #endif
179
180         dhcp_put_unaligned(data, (uint32_t *) &option[OPT_DATA]);
181         dhcp_add_binary_option(packet, option);
182
183         return;
184 }
185
186 void dhcp_init_header(struct dhcp_packet *packet, char type)
187 {
188         memset(packet, 0, sizeof(*packet));
189
190         packet->op = BOOTREQUEST;
191
192         switch (type) {
193         case DHCPOFFER:
194         case DHCPACK:
195         case DHCPNAK:
196                 packet->op = BOOTREPLY;
197         }
198
199         packet->htype = 1;
200         packet->hlen = 6;
201         packet->cookie = htonl(DHCP_MAGIC);
202         packet->options[0] = DHCP_END;
203
204         dhcp_add_simple_option(packet, DHCP_MESSAGE_TYPE, type);
205 }
206
207 static gboolean check_vendor(uint8_t  *option_vendor, const char *vendor)
208 {
209         uint8_t vendor_length = sizeof(vendor) - 1;
210
211         if (option_vendor[OPT_LEN - OPT_DATA] != vendor_length)
212                 return FALSE;
213
214         if (memcmp(option_vendor, vendor, vendor_length) != 0)
215                 return FALSE;
216
217         return TRUE;
218 }
219
220 static void check_broken_vendor(struct dhcp_packet *packet)
221 {
222         uint8_t *vendor;
223
224         if (packet->op != BOOTREQUEST)
225                 return;
226
227         vendor = dhcp_get_option(packet, DHCP_VENDOR);
228         if (vendor == NULL)
229                 return;
230
231         if (check_vendor(vendor, "MSFT 98") == TRUE)
232                 packet->flags |= htons(BROADCAST_FLAG);
233 }
234
235 int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd)
236 {
237         int n;
238
239         memset(packet, 0, sizeof(*packet));
240
241         n = read(fd, packet, sizeof(*packet));
242         if (n < 0)
243                 return -errno;
244
245         if (packet->cookie != htonl(DHCP_MAGIC))
246                 return -EPROTO;
247
248         check_broken_vendor(packet);
249
250         return n;
251 }
252
253 /* TODO: Use glib checksum */
254 uint16_t dhcp_checksum(void *addr, int count)
255 {
256         /*
257          * Compute Internet Checksum for "count" bytes
258          * beginning at location "addr".
259          */
260         int32_t sum = 0;
261         uint16_t *source = (uint16_t *) addr;
262
263         while (count > 1)  {
264                 /*  This is the inner loop */
265                 sum += *source++;
266                 count -= 2;
267         }
268
269         /*  Add left-over byte, if any */
270         if (count > 0) {
271                 /* Make sure that the left-over byte is added correctly both
272                  * with little and big endian hosts */
273                 uint16_t tmp = 0;
274                 *(uint8_t *) &tmp = *(uint8_t *) source;
275                 sum += tmp;
276         }
277         /*  Fold 32-bit sum to 16 bits */
278         while (sum >> 16)
279                 sum = (sum & 0xffff) + (sum >> 16);
280
281         return ~sum;
282 }
283
284 int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
285                 uint32_t source_ip, int source_port, uint32_t dest_ip,
286                         int dest_port, const uint8_t *dest_arp, int ifindex)
287 {
288         struct sockaddr_ll dest;
289         struct ip_udp_dhcp_packet packet;
290         int fd, n;
291
292         enum {
293                 IP_UPD_DHCP_SIZE = sizeof(struct ip_udp_dhcp_packet) -
294                                                 EXTEND_FOR_BUGGY_SERVERS,
295                 UPD_DHCP_SIZE = IP_UPD_DHCP_SIZE -
296                                 offsetof(struct ip_udp_dhcp_packet, udp),
297         };
298
299         fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
300         if (fd < 0)
301                 return -errno;
302
303         memset(&dest, 0, sizeof(dest));
304         memset(&packet, 0, sizeof(packet));
305         packet.data = *dhcp_pkt;
306
307         dest.sll_family = AF_PACKET;
308         dest.sll_protocol = htons(ETH_P_IP);
309         dest.sll_ifindex = ifindex;
310         dest.sll_halen = 6;
311         memcpy(dest.sll_addr, dest_arp, 6);
312         if (bind(fd, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
313                 close(fd);
314                 return -errno;
315         }
316
317         packet.ip.protocol = IPPROTO_UDP;
318         packet.ip.saddr = source_ip;
319         packet.ip.daddr = dest_ip;
320         packet.udp.source = htons(source_port);
321         packet.udp.dest = htons(dest_port);
322         /* size, excluding IP header: */
323         packet.udp.len = htons(UPD_DHCP_SIZE);
324         /* for UDP checksumming, ip.len is set to UDP packet len */
325         packet.ip.tot_len = packet.udp.len;
326         packet.udp.check = dhcp_checksum(&packet, IP_UPD_DHCP_SIZE);
327         /* but for sending, it is set to IP packet len */
328         packet.ip.tot_len = htons(IP_UPD_DHCP_SIZE);
329         packet.ip.ihl = sizeof(packet.ip) >> 2;
330         packet.ip.version = IPVERSION;
331         packet.ip.ttl = IPDEFTTL;
332         packet.ip.check = dhcp_checksum(&packet.ip, sizeof(packet.ip));
333
334         /*
335          * Currently we send full-sized DHCP packets (zero padded).
336          * If you need to change this: last byte of the packet is
337          * packet.data.options[dhcp_end_option(packet.data.options)]
338          */
339         n = sendto(fd, &packet, IP_UPD_DHCP_SIZE, 0,
340                         (struct sockaddr *) &dest, sizeof(dest));
341         if (n < 0)
342                 return -errno;
343
344         close(fd);
345
346         return n;
347 }
348
349 int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt,
350                                 uint32_t source_ip, int source_port,
351                                 uint32_t dest_ip, int dest_port)
352 {
353         struct sockaddr_in client;
354         int fd, n, opt = 1;
355
356         enum {
357                 DHCP_SIZE = sizeof(struct dhcp_packet) -
358                                         EXTEND_FOR_BUGGY_SERVERS,
359         };
360
361         fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
362         if (fd < 0)
363                 return -errno;
364
365         setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
366
367         memset(&client, 0, sizeof(client));
368         client.sin_family = AF_INET;
369         client.sin_port = htons(source_port);
370         client.sin_addr.s_addr = source_ip;
371         if (bind(fd, (struct sockaddr *) &client, sizeof(client)) < 0) {
372                 close(fd);
373                 return -errno;
374         }
375
376         memset(&client, 0, sizeof(client));
377         client.sin_family = AF_INET;
378         client.sin_port = htons(dest_port);
379         client.sin_addr.s_addr = dest_ip;
380         if (connect(fd, (struct sockaddr *) &client, sizeof(client)) < 0) {
381                 close(fd);
382                 return -errno;
383         }
384
385         n = write(fd, dhcp_pkt, DHCP_SIZE);
386
387         close(fd);
388
389         if (n < 0)
390                 return -errno;
391
392         return n;
393 }
394
395 int dhcp_l3_socket(int port, const char *interface)
396 {
397         int fd, opt = 1;
398         struct sockaddr_in addr;
399
400         fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
401
402         setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
403
404         if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
405                                 interface, strlen(interface) + 1) < 0) {
406                 close(fd);
407                 return -1;
408         }
409
410         memset(&addr, 0, sizeof(addr));
411         addr.sin_family = AF_INET;
412         addr.sin_port = htons(port);
413         if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
414                 close(fd);
415                 return -1;
416         }
417
418         return fd;
419 }