chiark / gitweb /
10b645759e111feb791070e8ba9bc5beefd3407c
[elogind.git] / src / libsystemd-network / dhcp-packet.c
1 /***
2   This file is part of systemd.
3
4   Copyright (C) 2013 Intel Corporation. All rights reserved.
5   Copyright (C) 2014 Tom Gundersen
6
7   systemd is free software; you can redistribute it and/or modify it
8   under the terms of the GNU Lesser General Public License as published by
9   the Free Software Foundation; either version 2.1 of the License, or
10   (at your option) any later version.
11
12   systemd is distributed in the hope that it will be useful, but
13   WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   Lesser General Public License for more details.
16
17   You should have received a copy of the GNU Lesser General Public License
18   along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <stdlib.h>
22 #include <errno.h>
23 #include <string.h>
24 #include <stdio.h>
25 #include <net/ethernet.h>
26 #include <net/if_arp.h>
27 #include <sys/param.h>
28
29 #include "util.h"
30 #include "list.h"
31
32 #include "dhcp-protocol.h"
33 #include "dhcp-lease-internal.h"
34 #include "dhcp-internal.h"
35 #include "sd-dhcp-lease.h"
36 #include "sd-dhcp-client.h"
37
38 #define DHCP_CLIENT_MIN_OPTIONS_SIZE            312
39
40 int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid,
41                       uint8_t type, uint8_t options[], size_t optlen, size_t *optoffset) {
42         size_t offset = 0;
43         int r;
44
45         assert(op == BOOTREQUEST || op == BOOTREPLY);
46
47         message->op = op;
48         message->htype = ARPHRD_ETHER;
49         message->hlen = ETHER_ADDR_LEN;
50         message->xid = htobe32(xid);
51         message->magic = htobe32(DHCP_MAGIC_COOKIE);
52
53         r = dhcp_option_append(message->options, optlen, &offset,
54                                  DHCP_OPTION_MESSAGE_TYPE, 1, &type);
55         if (r < 0)
56                 return r;
57
58         *optoffset = offset;
59
60         return 0;
61 }
62
63 uint16_t dhcp_packet_checksum(void *buf, size_t len) {
64         uint64_t *buf_64 = buf;
65         uint64_t *end_64 = (uint64_t*)buf + (len / sizeof(uint64_t));
66         uint32_t *buf_32;
67         uint16_t *buf_16;
68         uint8_t *buf_8;
69         uint64_t sum = 0;
70
71         while (buf_64 < end_64) {
72                 sum += *buf_64;
73                 if (sum < *buf_64)
74                         sum++;
75
76                 buf_64 ++;
77         }
78
79         buf_32 = (uint32_t*)buf_64;
80
81         if (len & sizeof(uint32_t)) {
82                 sum += *buf_32;
83                 if (sum < *buf_32)
84                         sum++;
85
86                 buf_32 ++;
87         }
88
89         buf_16 = (uint16_t*)buf_32;
90
91         if (len & sizeof(uint16_t)) {
92                 sum += *buf_16;
93                 if (sum < *buf_16)
94                         sum ++;
95
96                 buf_16 ++;
97         }
98
99         buf_8 = (uint8_t*)buf_16;
100
101         if (len & sizeof(uint8_t)) {
102                 sum += *buf_8;
103                 if (sum < *buf_8)
104                         sum++;
105         }
106
107         while (sum >> 16)
108                 sum = (sum & 0xffff) + (sum >> 16);
109
110         return ~sum;
111 }
112
113 void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr,
114                                    uint16_t source_port, be32_t destination_addr,
115                                    uint16_t destination_port, uint16_t len) {
116         packet->ip.version = IPVERSION;
117         packet->ip.ihl = DHCP_IP_SIZE / 4;
118         packet->ip.tot_len = htobe16(len);
119
120         packet->ip.tos = IPTOS_CLASS_CS6;
121
122         packet->ip.protocol = IPPROTO_UDP;
123         packet->ip.saddr = source_addr;
124         packet->ip.daddr = destination_addr;
125
126         packet->udp.source = htobe16(source_port);
127         packet->udp.dest = htobe16(destination_port);
128
129         packet->udp.len = htobe16(len - DHCP_IP_SIZE);
130
131         packet->ip.check = packet->udp.len;
132         packet->udp.check = dhcp_packet_checksum(&packet->ip.ttl, len - 8);
133
134         packet->ip.ttl = IPDEFTTL;
135         packet->ip.check = 0;
136         packet->ip.check = dhcp_packet_checksum(&packet->ip, DHCP_IP_SIZE);
137 }
138
139 int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum) {
140         size_t hdrlen;
141
142         assert(packet);
143
144         /* IP */
145
146         if (packet->ip.version != IPVERSION) {
147                 log_debug("ignoring packet: not IPv4");
148                 return -EINVAL;
149         }
150
151         if (packet->ip.ihl < 5) {
152                 log_debug("ignoring packet: IPv4 IHL (%u words) invalid",
153                           packet->ip.ihl);
154                 return -EINVAL;
155         }
156
157         hdrlen = packet->ip.ihl * 4;
158         if (hdrlen < 20) {
159                 log_debug("ignoring packet: IPv4 IHL (%zu bytes) "
160                           "smaller than minimum (20 bytes)", hdrlen);
161                 return -EINVAL;
162         }
163
164         if (len < hdrlen) {
165                 log_debug("ignoring packet: packet (%zu bytes) "
166                           "smaller than expected (%zu) by IP header", len,
167                           hdrlen);
168                 return -EINVAL;
169         }
170
171         /* UDP */
172
173         if (packet->ip.protocol != IPPROTO_UDP) {
174                 log_debug("ignoring packet: not UDP");
175                 return -EINVAL;
176         }
177
178         if (len < hdrlen + be16toh(packet->udp.len)) {
179                 log_debug("ignoring packet: packet (%zu bytes) "
180                           "smaller than expected (%zu) by UDP header", len,
181                           hdrlen + be16toh(packet->udp.len));
182                 return -EINVAL;
183         }
184
185         if (be16toh(packet->udp.dest) != DHCP_PORT_CLIENT) {
186                 log_debug("ignoring packet: to port %u, which "
187                           "is not the DHCP client port (%u)",
188                           be16toh(packet->udp.dest), DHCP_PORT_CLIENT);
189                 return -EINVAL;
190         }
191
192         /* checksums - computing these is relatively expensive, so only do it
193            if all the other checks have passed
194          */
195
196         if (dhcp_packet_checksum(&packet->ip, hdrlen)) {
197                 log_debug("ignoring packet: invalid IP checksum");
198                 return -EINVAL;
199         }
200
201         if (checksum && packet->udp.check) {
202                 packet->ip.check = packet->udp.len;
203                 packet->ip.ttl = 0;
204
205                 if (dhcp_packet_checksum(&packet->ip.ttl,
206                                   be16toh(packet->udp.len) + 12)) {
207                         log_debug("ignoring packet: invalid UDP checksum");
208                         return -EINVAL;
209                 }
210         }
211
212         return 0;
213 }