chiark / gitweb /
machine: make sure unpriviliged "machinectl status" can show the machine's OS version
[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, 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, optlen, &offset, 0,
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(uint8_t *buf, size_t len) {
64         uint64_t *buf_64 = (uint64_t*)buf;
65         uint64_t *end_64 = buf_64 + (len / sizeof(uint64_t));
66         uint64_t sum = 0;
67
68         /* See RFC1071 */
69
70         while (buf_64 < end_64) {
71                 sum += *buf_64;
72                 if (sum < *buf_64)
73                         /* wrap around in one's complement */
74                         sum++;
75
76                 buf_64 ++;
77         }
78
79         if (len % sizeof(uint64_t)) {
80                 /* If the buffer is not aligned to 64-bit, we need
81                    to zero-pad the last few bytes and add them in */
82                 uint64_t buf_tail = 0;
83
84                 memcpy(&buf_tail, buf_64, len % sizeof(uint64_t));
85
86                 sum += buf_tail;
87                 if (sum < buf_tail)
88                         /* wrap around */
89                         sum++;
90         }
91
92         while (sum >> 16)
93                 sum = (sum & 0xffff) + (sum >> 16);
94
95         return ~sum;
96 }
97
98 void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr,
99                                    uint16_t source_port, be32_t destination_addr,
100                                    uint16_t destination_port, uint16_t len) {
101         packet->ip.version = IPVERSION;
102         packet->ip.ihl = DHCP_IP_SIZE / 4;
103         packet->ip.tot_len = htobe16(len);
104
105         packet->ip.tos = IPTOS_CLASS_CS6;
106
107         packet->ip.protocol = IPPROTO_UDP;
108         packet->ip.saddr = source_addr;
109         packet->ip.daddr = destination_addr;
110
111         packet->udp.source = htobe16(source_port);
112         packet->udp.dest = htobe16(destination_port);
113
114         packet->udp.len = htobe16(len - DHCP_IP_SIZE);
115
116         packet->ip.check = packet->udp.len;
117         packet->udp.check = dhcp_packet_checksum((uint8_t*)&packet->ip.ttl, len - 8);
118
119         packet->ip.ttl = IPDEFTTL;
120         packet->ip.check = 0;
121         packet->ip.check = dhcp_packet_checksum((uint8_t*)&packet->ip, DHCP_IP_SIZE);
122 }
123
124 int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum) {
125         size_t hdrlen;
126
127         assert(packet);
128
129         /* IP */
130
131         if (packet->ip.version != IPVERSION) {
132                 log_debug("ignoring packet: not IPv4");
133                 return -EINVAL;
134         }
135
136         if (packet->ip.ihl < 5) {
137                 log_debug("ignoring packet: IPv4 IHL (%u words) invalid",
138                           packet->ip.ihl);
139                 return -EINVAL;
140         }
141
142         hdrlen = packet->ip.ihl * 4;
143         if (hdrlen < 20) {
144                 log_debug("ignoring packet: IPv4 IHL (%zu bytes) "
145                           "smaller than minimum (20 bytes)", hdrlen);
146                 return -EINVAL;
147         }
148
149         if (len < hdrlen) {
150                 log_debug("ignoring packet: packet (%zu bytes) "
151                           "smaller than expected (%zu) by IP header", len,
152                           hdrlen);
153                 return -EINVAL;
154         }
155
156         /* UDP */
157
158         if (packet->ip.protocol != IPPROTO_UDP) {
159                 log_debug("ignoring packet: not UDP");
160                 return -EINVAL;
161         }
162
163         if (len < hdrlen + be16toh(packet->udp.len)) {
164                 log_debug("ignoring packet: packet (%zu bytes) "
165                           "smaller than expected (%zu) by UDP header", len,
166                           hdrlen + be16toh(packet->udp.len));
167                 return -EINVAL;
168         }
169
170         if (be16toh(packet->udp.dest) != DHCP_PORT_CLIENT) {
171                 log_debug("ignoring packet: to port %u, which "
172                           "is not the DHCP client port (%u)",
173                           be16toh(packet->udp.dest), DHCP_PORT_CLIENT);
174                 return -EINVAL;
175         }
176
177         /* checksums - computing these is relatively expensive, so only do it
178            if all the other checks have passed
179          */
180
181         if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen)) {
182                 log_debug("ignoring packet: invalid IP checksum");
183                 return -EINVAL;
184         }
185
186         if (checksum && packet->udp.check) {
187                 packet->ip.check = packet->udp.len;
188                 packet->ip.ttl = 0;
189
190                 if (dhcp_packet_checksum((uint8_t*)&packet->ip.ttl,
191                                   be16toh(packet->udp.len) + 12)) {
192                         log_debug("ignoring packet: invalid UDP checksum");
193                         return -EINVAL;
194                 }
195         }
196
197         return 0;
198 }