/src/freeradius-server/src/protocols/dhcpv4/raw.c
Line | Count | Source |
1 | | /* |
2 | | * This library is free software; you can redistribute it and/or |
3 | | * modify it under the terms of the GNU Lesser General Public |
4 | | * License as published by the Free Software Foundation; either |
5 | | * version 2.1 of the License, or (at your option) any later version. |
6 | | * |
7 | | * This library is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
10 | | * Lesser General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU Lesser General Public |
13 | | * License along with this library; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: 08715191a0c58ede0d58e2563dc46580b1760135 $ |
19 | | * |
20 | | * @file protocols/dhcpv4/raw.c |
21 | | * @brief Send/recv DHCP packets using raw sockets. |
22 | | * |
23 | | * @copyright 2008,2017 The FreeRADIUS server project |
24 | | * @copyright 2008 Alan DeKok (aland@deployingradius.com) |
25 | | */ |
26 | | #include "attrs.h" |
27 | | #include "dhcpv4.h" |
28 | | |
29 | | #include <freeradius-devel/util/net.h> |
30 | | #include <freeradius-devel/util/pair.h> |
31 | | #include <freeradius-devel/util/proto.h> |
32 | | #include <freeradius-devel/util/syserror.h> |
33 | | #include <freeradius-devel/util/udpfromto.h> |
34 | | |
35 | | #include <sys/ioctl.h> |
36 | | |
37 | | #ifdef HAVE_SYS_SOCKET_H |
38 | | #endif |
39 | | #ifdef HAVE_SYS_TYPES_H |
40 | | #endif |
41 | | |
42 | | #ifdef HAVE_LINUX_IF_PACKET_H |
43 | | # include <linux/if_ether.h> |
44 | | #endif |
45 | | |
46 | | #include <net/if_arp.h> |
47 | | |
48 | | #ifdef HAVE_LINUX_IF_PACKET_H |
49 | | /** Open a raw socket to read/write packets from/to |
50 | | * |
51 | | * @param[out] link_layer A sockaddr_ll struct to populate. Must be passed to other raw |
52 | | * functions. |
53 | | * @param[in] ifindex of the interface we're binding to. |
54 | | * @return |
55 | | * - >= 0 a file descriptor to read/write packets on. |
56 | | * - <0 an error occurred. |
57 | | */ |
58 | | int fr_dhcpv4_raw_socket_open(struct sockaddr_ll *link_layer, int ifindex) |
59 | 0 | { |
60 | 0 | int fd; |
61 | | |
62 | | /* |
63 | | * PF_PACKET - packet interface on device level. |
64 | | * using a raw socket allows packet data to be unchanged by the device driver. |
65 | | */ |
66 | 0 | fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); |
67 | 0 | if (fd < 0) { |
68 | 0 | fr_strerror_printf("Cannot open socket: %s", fr_syserror(errno)); |
69 | 0 | return fd; |
70 | 0 | } |
71 | | |
72 | | /* Set link layer parameters */ |
73 | 0 | memset(link_layer, 0, sizeof(struct sockaddr_ll)); |
74 | |
|
75 | 0 | link_layer->sll_family = AF_PACKET; |
76 | 0 | link_layer->sll_protocol = htons(ETH_P_ALL); |
77 | 0 | link_layer->sll_ifindex = ifindex; |
78 | 0 | link_layer->sll_hatype = ARPHRD_ETHER; |
79 | 0 | link_layer->sll_pkttype = PACKET_OTHERHOST; |
80 | 0 | link_layer->sll_halen = 6; |
81 | |
|
82 | 0 | if (bind(fd, (struct sockaddr *)link_layer, sizeof(struct sockaddr_ll)) < 0) { |
83 | 0 | close(fd); |
84 | 0 | fr_strerror_printf("Cannot bind raw socket: %s", fr_syserror(errno)); |
85 | 0 | return -1; |
86 | 0 | } |
87 | | |
88 | 0 | return fd; |
89 | 0 | } |
90 | | |
91 | | /** Create the requisite L2/L3 headers, and write a DHCPv4 packet to a raw socket |
92 | | * |
93 | | * @param[in] sockfd to write to. |
94 | | * @param[in] link_layer information, as returned by fr_dhcpv4_raw_socket_open. |
95 | | * @param[in] packet to write. |
96 | | * @param[in] list to send. |
97 | | * @return |
98 | | * - 0 on success. |
99 | | * - -1 on failure. |
100 | | */ |
101 | | int fr_dhcpv4_raw_packet_send(int sockfd, struct sockaddr_ll *link_layer, |
102 | | fr_packet_t *packet, fr_pair_list_t *list) |
103 | 0 | { |
104 | 0 | uint8_t dhcp_packet[1518] = { 0 }; |
105 | 0 | ethernet_header_t *eth_hdr = (ethernet_header_t *)dhcp_packet; |
106 | 0 | ip_header_t *ip_hdr = (ip_header_t *)(dhcp_packet + ETH_HDR_SIZE); |
107 | 0 | udp_header_t *udp_hdr = (udp_header_t *) (dhcp_packet + ETH_HDR_SIZE + IP_HDR_SIZE); |
108 | 0 | dhcp_packet_t *dhcp = (dhcp_packet_t *)(dhcp_packet + ETH_HDR_SIZE + IP_HDR_SIZE + UDP_HDR_SIZE); |
109 | |
|
110 | 0 | uint16_t l4_len = (UDP_HDR_SIZE + packet->data_len); |
111 | 0 | fr_pair_t *vp; |
112 | | |
113 | | /* set ethernet source address to our MAC address (Client-Hardware-Address). */ |
114 | 0 | uint8_t dhmac[ETH_ADDR_LEN] = { 0 }; |
115 | 0 | if ((vp = fr_pair_find_by_da(list, NULL, attr_dhcp_client_hardware_address))) { |
116 | 0 | if (vp->vp_type == FR_TYPE_ETHERNET) memcpy(dhmac, vp->vp_ether, sizeof(vp->vp_ether)); |
117 | 0 | } |
118 | | |
119 | | /* fill in Ethernet layer (L2) */ |
120 | 0 | memcpy(eth_hdr->dst_addr, eth_bcast, ETH_ADDR_LEN); |
121 | 0 | memcpy(eth_hdr->src_addr, dhmac, ETH_ADDR_LEN); |
122 | 0 | eth_hdr->ether_type = htons(ETH_TYPE_IP); |
123 | | |
124 | | /* fill in IP layer (L3) */ |
125 | 0 | ip_hdr->ip_vhl = IP_VHL(4, 5); |
126 | 0 | ip_hdr->ip_tos = 0; |
127 | 0 | ip_hdr->ip_len = htons(IP_HDR_SIZE + UDP_HDR_SIZE + packet->data_len); |
128 | 0 | ip_hdr->ip_id = 0; |
129 | 0 | ip_hdr->ip_off = 0; |
130 | 0 | ip_hdr->ip_ttl = 64; |
131 | 0 | ip_hdr->ip_p = 17; |
132 | 0 | ip_hdr->ip_sum = 0; /* Filled later */ |
133 | | |
134 | | /* saddr: packet src IP addr (default: 0.0.0.0). */ |
135 | 0 | ip_hdr->ip_src.s_addr = packet->socket.inet.src_ipaddr.addr.v4.s_addr; |
136 | | |
137 | | /* daddr: packet destination IP addr (should be 255.255.255.255 for broadcast). */ |
138 | 0 | ip_hdr->ip_dst.s_addr = packet->socket.inet.dst_ipaddr.addr.v4.s_addr; |
139 | | |
140 | | /* IP header checksum */ |
141 | 0 | ip_hdr->ip_sum = fr_ip_header_checksum((uint8_t const *)ip_hdr, 5); |
142 | |
|
143 | 0 | udp_hdr->src = htons(packet->socket.inet.src_port); |
144 | 0 | udp_hdr->dst = htons(packet->socket.inet.dst_port); |
145 | |
|
146 | 0 | udp_hdr->len = htons(l4_len); |
147 | 0 | udp_hdr->checksum = 0; /* UDP checksum will be done after dhcp header */ |
148 | | |
149 | | /* DHCP layer (L7) */ |
150 | | |
151 | | /* just copy what FreeRADIUS has encoded for us. */ |
152 | 0 | memcpy(dhcp, packet->data, packet->data_len); |
153 | | |
154 | | /* UDP checksum is done here */ |
155 | 0 | udp_hdr->checksum = fr_udp_checksum((uint8_t const *)(dhcp_packet + ETH_HDR_SIZE + IP_HDR_SIZE), |
156 | 0 | l4_len, udp_hdr->checksum, |
157 | 0 | packet->socket.inet.src_ipaddr.addr.v4, packet->socket.inet.dst_ipaddr.addr.v4); |
158 | |
|
159 | 0 | return sendto(sockfd, dhcp_packet, (ETH_HDR_SIZE + IP_HDR_SIZE + UDP_HDR_SIZE + packet->data_len), |
160 | 0 | 0, (struct sockaddr *) link_layer, sizeof(struct sockaddr_ll)); |
161 | 0 | } |
162 | | |
163 | | /* |
164 | | * For a client, receive a DHCP packet from a raw packet |
165 | | * socket. Make sure it matches the ongoing request. |
166 | | * |
167 | | * FIXME: split this into two, recv_raw_packet, and verify(packet, original) |
168 | | */ |
169 | | fr_packet_t *fr_dhcpv4_raw_packet_recv(int sockfd, struct sockaddr_ll *link_layer, |
170 | | fr_packet_t *request, fr_pair_list_t *list) |
171 | 0 | { |
172 | 0 | fr_pair_t *vp; |
173 | 0 | fr_packet_t *packet; |
174 | 0 | dhcp_packet_t *dhcp_data; |
175 | 0 | uint8_t const *code; |
176 | 0 | uint32_t magic, xid; |
177 | 0 | ssize_t data_len; |
178 | |
|
179 | 0 | uint8_t *raw_packet; |
180 | 0 | ethernet_header_t *eth_hdr; |
181 | 0 | ip_header_t *ip_hdr; |
182 | 0 | udp_header_t *udp_hdr; |
183 | 0 | dhcp_packet_t *dhcp_hdr; |
184 | 0 | uint16_t udp_src_port; |
185 | 0 | uint16_t udp_dst_port; |
186 | 0 | size_t dhcp_data_len; |
187 | 0 | socklen_t sock_len; |
188 | 0 | uint8_t data_offset; |
189 | |
|
190 | 0 | packet = fr_packet_alloc(NULL, false); |
191 | 0 | if (!packet) { |
192 | 0 | fr_strerror_const("Failed allocating packet"); |
193 | 0 | return NULL; |
194 | 0 | } |
195 | | |
196 | 0 | raw_packet = talloc_zero_array(packet, uint8_t, MAX_PACKET_SIZE); |
197 | 0 | if (!raw_packet) { |
198 | 0 | fr_strerror_const("Out of memory"); |
199 | 0 | fr_packet_free(&packet); |
200 | 0 | return NULL; |
201 | 0 | } |
202 | | |
203 | 0 | packet->socket.fd = sockfd; |
204 | | |
205 | | /* a packet was received (but maybe it is not for us) */ |
206 | 0 | sock_len = sizeof(struct sockaddr_ll); |
207 | 0 | data_len = recvfrom(sockfd, raw_packet, MAX_PACKET_SIZE, 0, (struct sockaddr *)link_layer, &sock_len); |
208 | |
|
209 | 0 | data_offset = ETH_HDR_SIZE + IP_HDR_SIZE + UDP_HDR_SIZE; /* DHCP data after Ethernet, IP, UDP */ |
210 | |
|
211 | 0 | if (data_len <= data_offset) DISCARD_RP("Payload (%d) smaller than required for layers 2+3+4", (int)data_len); |
212 | | |
213 | | /* map raw packet to packet header of the different layers (Ethernet, IP, UDP) */ |
214 | 0 | eth_hdr = (ethernet_header_t *)raw_packet; |
215 | | |
216 | | /* |
217 | | * Check Ethernet layer data (L2) |
218 | | */ |
219 | 0 | if (ntohs(eth_hdr->ether_type) != ETH_TYPE_IP) DISCARD_RP("Ethernet type (%d) != IP", |
220 | 0 | ntohs(eth_hdr->ether_type)); |
221 | | |
222 | | /* |
223 | | * If Ethernet destination is not broadcast (ff:ff:ff:ff:ff:ff) |
224 | | * Check if it matches the source HW address used (Client-Hardware-Address = 267) |
225 | | */ |
226 | 0 | if ((memcmp(ð_bcast, ð_hdr->dst_addr, ETH_ADDR_LEN) != 0) && |
227 | 0 | (vp = fr_pair_find_by_da(list, NULL, attr_dhcp_client_hardware_address)) && |
228 | 0 | ((vp->vp_type == FR_TYPE_ETHERNET) && (memcmp(vp->vp_ether, ð_hdr->dst_addr, ETH_ADDR_LEN) != 0))) { |
229 | | |
230 | | /* No match. */ |
231 | 0 | DISCARD_RP("Ethernet destination (%pV) is not broadcast and doesn't match request source (%pV)", |
232 | 0 | fr_box_ether(eth_hdr->dst_addr), &vp->data); |
233 | 0 | } |
234 | | |
235 | | /* |
236 | | * Ethernet is OK. Now look at IP. |
237 | | */ |
238 | 0 | ip_hdr = (ip_header_t *)(raw_packet + ETH_HDR_SIZE); |
239 | | |
240 | | /* |
241 | | * Check IPv4 layer data (L3) |
242 | | */ |
243 | 0 | if (ip_hdr->ip_p != IPPROTO_UDP) DISCARD_RP("IP protocol (%d) != UDP", ip_hdr->ip_p); |
244 | | |
245 | | /* |
246 | | * note: checking the destination IP address is not |
247 | | * useful (it would be the offered IP address - which we |
248 | | * don't know beforehand, or the broadcast address). |
249 | | */ |
250 | | |
251 | | /* |
252 | | * Now check UDP. |
253 | | */ |
254 | 0 | udp_hdr = (udp_header_t *)(raw_packet + ETH_HDR_SIZE + IP_HDR_SIZE); |
255 | | |
256 | | /* |
257 | | * Check UDP layer data (L4) |
258 | | */ |
259 | 0 | udp_src_port = ntohs(udp_hdr->src); |
260 | 0 | udp_dst_port = ntohs(udp_hdr->dst); |
261 | | |
262 | | /* |
263 | | * Check DHCP layer data |
264 | | */ |
265 | 0 | dhcp_data_len = data_len - data_offset; |
266 | |
|
267 | 0 | if (dhcp_data_len < MIN_PACKET_SIZE) DISCARD_RP("DHCP packet is too small (%zu < %i)", |
268 | 0 | dhcp_data_len, MIN_PACKET_SIZE); |
269 | 0 | if (dhcp_data_len > MAX_PACKET_SIZE) DISCARD_RP("DHCP packet is too large (%zu > %i)", |
270 | 0 | dhcp_data_len, MAX_PACKET_SIZE); |
271 | |
|
272 | 0 | dhcp_hdr = (dhcp_packet_t *)(raw_packet + ETH_HDR_SIZE + IP_HDR_SIZE + UDP_HDR_SIZE); |
273 | |
|
274 | 0 | if (dhcp_hdr->htype != 1) DISCARD_RP("DHCP hardware type (%d) != Ethernet (1)", dhcp_hdr->htype); |
275 | 0 | if (dhcp_hdr->hlen != 6) DISCARD_RP("DHCP hardware address length (%d) != 6", dhcp_hdr->hlen); |
276 | |
|
277 | 0 | magic = ntohl(dhcp_hdr->option_format); |
278 | |
|
279 | 0 | if (magic != DHCP_OPTION_MAGIC_NUMBER) DISCARD_RP("DHCP magic cookie (0x%04x) != DHCP (0x%04x)", |
280 | 0 | magic, DHCP_OPTION_MAGIC_NUMBER); |
281 | | |
282 | | /* |
283 | | * Reply transaction id must match value from request. |
284 | | */ |
285 | 0 | xid = ntohl(dhcp_hdr->xid); |
286 | 0 | if (xid != (uint32_t)request->id) DISCARD_RP("DHCP transaction ID (0x%04x) != xid from request (0x%04x)", |
287 | 0 | xid, request->id) |
288 | | |
289 | | /* |
290 | | * all checks ok! this is a DHCP reply we're interested in. |
291 | | * |
292 | | * dhcp_data is present to avoid what appears to coverity |
293 | | * to be a cast from a less aligned type to a more aligned |
294 | | * type in the fr_dhcpv4_packet_get_option() call, even though |
295 | | * talloc_memdup() returns a pointer aligned to TALLOC_ALIGN |
296 | | * bytes. |
297 | | */ |
298 | 0 | packet->data_len = dhcp_data_len; |
299 | 0 | dhcp_data = talloc_memdup(packet, raw_packet + data_offset, dhcp_data_len); |
300 | 0 | packet->data = (uint8_t *) dhcp_data; |
301 | 0 | TALLOC_FREE(raw_packet); |
302 | 0 | packet->id = xid; |
303 | |
|
304 | 0 | code = fr_dhcpv4_packet_get_option((dhcp_packet_t const *)dhcp_data, |
305 | 0 | packet->data_len, attr_dhcp_message_type); |
306 | 0 | if (!code) { |
307 | 0 | fr_strerror_const("No message-type option was found in the packet"); |
308 | 0 | fr_packet_free(&packet); |
309 | 0 | return NULL; |
310 | 0 | } |
311 | | |
312 | 0 | if ((code[1] < 1) || (code[2] == 0) || (code[2] >= FR_DHCP_CODE_MAX)) { |
313 | 0 | fr_strerror_const("Unknown value for message-type option"); |
314 | 0 | fr_packet_free(&packet); |
315 | 0 | return NULL; |
316 | 0 | } |
317 | | |
318 | 0 | packet->code = code[2]; |
319 | |
|
320 | 0 | packet->socket.inet.src_port = udp_src_port; |
321 | 0 | packet->socket.inet.dst_port = udp_dst_port; |
322 | |
|
323 | 0 | packet->socket.inet.src_ipaddr.af = AF_INET; |
324 | 0 | packet->socket.inet.src_ipaddr.addr.v4.s_addr = ip_hdr->ip_src.s_addr; |
325 | 0 | packet->socket.inet.dst_ipaddr.af = AF_INET; |
326 | 0 | packet->socket.inet.dst_ipaddr.addr.v4.s_addr = ip_hdr->ip_dst.s_addr; |
327 | |
|
328 | 0 | return packet; |
329 | 0 | } |
330 | | #endif |