Coverage Report

Created: 2026-08-08 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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(&eth_bcast, &eth_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, &eth_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