Line data Source code
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 <errno.h>
22 : #include <string.h>
23 : #include <net/ethernet.h>
24 : #include <net/if_arp.h>
25 :
26 :
27 : #include "dhcp-protocol.h"
28 : #include "dhcp-internal.h"
29 :
30 : #define DHCP_CLIENT_MIN_OPTIONS_SIZE 312
31 :
32 4 : int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid,
33 : uint8_t type, uint16_t arp_type, size_t optlen,
34 : size_t *optoffset) {
35 4 : size_t offset = 0;
36 : int r;
37 :
38 4 : assert(op == BOOTREQUEST || op == BOOTREPLY);
39 4 : assert(arp_type == ARPHRD_ETHER || arp_type == ARPHRD_INFINIBAND);
40 :
41 4 : message->op = op;
42 4 : message->htype = arp_type;
43 4 : message->hlen = (arp_type == ARPHRD_ETHER) ? ETHER_ADDR_LEN : 0;
44 4 : message->xid = htobe32(xid);
45 4 : message->magic = htobe32(DHCP_MAGIC_COOKIE);
46 :
47 4 : r = dhcp_option_append(message, optlen, &offset, 0,
48 : DHCP_OPTION_MESSAGE_TYPE, 1, &type);
49 4 : if (r < 0)
50 0 : return r;
51 :
52 4 : *optoffset = offset;
53 :
54 4 : return 0;
55 : }
56 :
57 15 : uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len) {
58 15 : uint64_t *buf_64 = (uint64_t*)buf;
59 15 : uint64_t *end_64 = buf_64 + (len / sizeof(uint64_t));
60 15 : uint64_t sum = 0;
61 :
62 : /* See RFC1071 */
63 :
64 272 : while (buf_64 < end_64) {
65 242 : sum += *buf_64;
66 242 : if (sum < *buf_64)
67 : /* wrap around in one's complement */
68 0 : sum++;
69 :
70 242 : buf_64 ++;
71 : }
72 :
73 15 : if (len % sizeof(uint64_t)) {
74 : /* If the buffer is not aligned to 64-bit, we need
75 : to zero-pad the last few bytes and add them in */
76 15 : uint64_t buf_tail = 0;
77 :
78 15 : memcpy(&buf_tail, buf_64, len % sizeof(uint64_t));
79 :
80 15 : sum += buf_tail;
81 15 : if (sum < buf_tail)
82 : /* wrap around */
83 0 : sum++;
84 : }
85 :
86 70 : while (sum >> 16)
87 40 : sum = (sum & 0xffff) + (sum >> 16);
88 :
89 15 : return ~sum;
90 : }
91 :
92 3 : void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr,
93 : uint16_t source_port, be32_t destination_addr,
94 : uint16_t destination_port, uint16_t len) {
95 3 : packet->ip.version = IPVERSION;
96 3 : packet->ip.ihl = DHCP_IP_SIZE / 4;
97 3 : packet->ip.tot_len = htobe16(len);
98 :
99 3 : packet->ip.tos = IPTOS_CLASS_CS6;
100 :
101 3 : packet->ip.protocol = IPPROTO_UDP;
102 3 : packet->ip.saddr = source_addr;
103 3 : packet->ip.daddr = destination_addr;
104 :
105 3 : packet->udp.source = htobe16(source_port);
106 3 : packet->udp.dest = htobe16(destination_port);
107 :
108 3 : packet->udp.len = htobe16(len - DHCP_IP_SIZE);
109 :
110 3 : packet->ip.check = packet->udp.len;
111 3 : packet->udp.check = dhcp_packet_checksum((uint8_t*)&packet->ip.ttl, len - 8);
112 :
113 3 : packet->ip.ttl = IPDEFTTL;
114 3 : packet->ip.check = 0;
115 3 : packet->ip.check = dhcp_packet_checksum((uint8_t*)&packet->ip, DHCP_IP_SIZE);
116 3 : }
117 :
118 2 : int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum) {
119 : size_t hdrlen;
120 :
121 2 : assert(packet);
122 :
123 : /* IP */
124 :
125 2 : if (packet->ip.version != IPVERSION) {
126 0 : log_debug("ignoring packet: not IPv4");
127 0 : return -EINVAL;
128 : }
129 :
130 2 : if (packet->ip.ihl < 5) {
131 0 : log_debug("ignoring packet: IPv4 IHL (%u words) invalid",
132 : packet->ip.ihl);
133 0 : return -EINVAL;
134 : }
135 :
136 2 : hdrlen = packet->ip.ihl * 4;
137 2 : if (hdrlen < 20) {
138 0 : log_debug("ignoring packet: IPv4 IHL (%zu bytes) "
139 : "smaller than minimum (20 bytes)", hdrlen);
140 0 : return -EINVAL;
141 : }
142 :
143 2 : if (len < hdrlen) {
144 0 : log_debug("ignoring packet: packet (%zu bytes) "
145 : "smaller than expected (%zu) by IP header", len,
146 : hdrlen);
147 0 : return -EINVAL;
148 : }
149 :
150 : /* UDP */
151 :
152 2 : if (packet->ip.protocol != IPPROTO_UDP) {
153 0 : log_debug("ignoring packet: not UDP");
154 0 : return -EINVAL;
155 : }
156 :
157 2 : if (len < hdrlen + be16toh(packet->udp.len)) {
158 0 : log_debug("ignoring packet: packet (%zu bytes) "
159 : "smaller than expected (%zu) by UDP header", len,
160 : hdrlen + be16toh(packet->udp.len));
161 0 : return -EINVAL;
162 : }
163 :
164 2 : if (be16toh(packet->udp.dest) != DHCP_PORT_CLIENT) {
165 0 : log_debug("ignoring packet: to port %u, which "
166 : "is not the DHCP client port (%u)",
167 : be16toh(packet->udp.dest), DHCP_PORT_CLIENT);
168 0 : return -EINVAL;
169 : }
170 :
171 : /* checksums - computing these is relatively expensive, so only do it
172 : if all the other checks have passed
173 : */
174 :
175 2 : if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen)) {
176 0 : log_debug("ignoring packet: invalid IP checksum");
177 0 : return -EINVAL;
178 : }
179 :
180 2 : if (checksum && packet->udp.check) {
181 0 : packet->ip.check = packet->udp.len;
182 0 : packet->ip.ttl = 0;
183 :
184 0 : if (dhcp_packet_checksum((uint8_t*)&packet->ip.ttl,
185 0 : be16toh(packet->udp.len) + 12)) {
186 0 : log_debug("ignoring packet: invalid UDP checksum");
187 0 : return -EINVAL;
188 : }
189 : }
190 :
191 2 : return 0;
192 : }
|