Coverage Report

Created: 2026-03-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/protocols/dhcpv4/base.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: 1f716612b59640f94466c13fa077746535721108 $
19
 *
20
 * @file protocols/dhcpv4/base.c
21
 * @brief Functions to send/receive dhcp packets.
22
 *
23
 * @copyright 2008 The FreeRADIUS server project
24
 * @copyright 2008 Alan DeKok (aland@deployingradius.com)
25
 */
26
RCSID("$Id: 1f716612b59640f94466c13fa077746535721108 $")
27
28
#include <freeradius-devel/dhcpv4/dhcpv4.h>
29
#include <freeradius-devel/util/net.h>
30
#include <freeradius-devel/util/proto.h>
31
#include "attrs.h"
32
33
static uint32_t instance_count = 0;
34
static bool instantiated = false;
35
36
typedef struct {
37
  uint8_t   code;
38
  uint8_t   length;
39
} dhcp_option_t;
40
41
fr_dict_t const *dict_dhcpv4;
42
43
extern fr_dict_autoload_t dhcpv4_dict[];
44
fr_dict_autoload_t dhcpv4_dict[] = {
45
  { .out = &dict_dhcpv4, .proto = "dhcpv4" },
46
47
  DICT_AUTOLOAD_TERMINATOR
48
};
49
50
fr_dict_attr_t const *attr_dhcp_boot_filename;
51
fr_dict_attr_t const *attr_dhcp_client_hardware_address;
52
fr_dict_attr_t const *attr_dhcp_client_ip_address;
53
fr_dict_attr_t const *attr_dhcp_flags;
54
fr_dict_attr_t const *attr_dhcp_gateway_ip_address;
55
fr_dict_attr_t const *attr_dhcp_hardware_address_length;
56
fr_dict_attr_t const *attr_dhcp_hardware_type;
57
fr_dict_attr_t const *attr_dhcp_hop_count;
58
fr_dict_attr_t const *attr_dhcp_number_of_seconds;
59
fr_dict_attr_t const *attr_dhcp_opcode;
60
fr_dict_attr_t const *attr_dhcp_server_host_name;
61
fr_dict_attr_t const *attr_dhcp_server_ip_address;
62
fr_dict_attr_t const *attr_dhcp_transaction_id;
63
fr_dict_attr_t const *attr_dhcp_your_ip_address;
64
fr_dict_attr_t const *attr_dhcp_dhcp_maximum_msg_size;
65
fr_dict_attr_t const *attr_dhcp_interface_mtu_size;
66
fr_dict_attr_t const *attr_dhcp_message_type;
67
fr_dict_attr_t const *attr_dhcp_parameter_request_list;
68
fr_dict_attr_t const *attr_dhcp_overload;
69
fr_dict_attr_t const *attr_dhcp_vendor_class_identifier;
70
fr_dict_attr_t const *attr_dhcp_relay_link_selection;
71
fr_dict_attr_t const *attr_dhcp_subnet_selection_option;
72
fr_dict_attr_t const *attr_dhcp_network_subnet;
73
fr_dict_attr_t const *attr_dhcp_option_82;
74
75
extern fr_dict_attr_autoload_t dhcpv4_dict_attr[];
76
fr_dict_attr_autoload_t dhcpv4_dict_attr[] = {
77
  { .out = &attr_dhcp_boot_filename, .name = "Boot-Filename", .type = FR_TYPE_STRING, .dict = &dict_dhcpv4 },
78
  { .out = &attr_dhcp_client_hardware_address, .name = "Client-Hardware-Address", .type = FR_TYPE_ETHERNET, .dict = &dict_dhcpv4 },
79
  { .out = &attr_dhcp_client_ip_address, .name = "Client-IP-Address", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4 },
80
  { .out = &attr_dhcp_flags, .name = "Flags", .type = FR_TYPE_UINT16, .dict = &dict_dhcpv4 },
81
  { .out = &attr_dhcp_gateway_ip_address, .name = "Gateway-IP-Address", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4 },
82
  { .out = &attr_dhcp_hardware_address_length, .name = "Hardware-Address-Length", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv4 },
83
  { .out = &attr_dhcp_hardware_type, .name = "Hardware-Type", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv4 },
84
  { .out = &attr_dhcp_hop_count, .name = "Hop-Count", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv4 },
85
  { .out = &attr_dhcp_number_of_seconds, .name = "Number-of-Seconds", .type = FR_TYPE_UINT16, .dict = &dict_dhcpv4 },
86
  { .out = &attr_dhcp_opcode, .name = "Opcode", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv4 },
87
  { .out = &attr_dhcp_server_host_name, .name = "Server-Host-Name", .type = FR_TYPE_STRING, .dict = &dict_dhcpv4 },
88
  { .out = &attr_dhcp_server_ip_address, .name = "Server-IP-Address", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4 },
89
  { .out = &attr_dhcp_transaction_id, .name = "Transaction-Id", .type = FR_TYPE_UINT32, .dict = &dict_dhcpv4 },
90
  { .out = &attr_dhcp_your_ip_address, .name = "Your-IP-Address", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4 },
91
  { .out = &attr_dhcp_dhcp_maximum_msg_size, .name = "Maximum-Msg-Size", .type = FR_TYPE_UINT16, .dict = &dict_dhcpv4 },
92
  { .out = &attr_dhcp_interface_mtu_size, .name = "Interface-MTU-Size", .type = FR_TYPE_UINT16, .dict = &dict_dhcpv4 },
93
  { .out = &attr_dhcp_message_type, .name = "Message-Type", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv4 },
94
  { .out = &attr_dhcp_parameter_request_list, .name = "Parameter-Request-List", .type = FR_TYPE_ATTR, .dict = &dict_dhcpv4 },
95
  { .out = &attr_dhcp_overload, .name = "Overload", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv4 },
96
  { .out = &attr_dhcp_vendor_class_identifier, .name = "Vendor-Class-Identifier", .type = FR_TYPE_OCTETS, .dict = &dict_dhcpv4 },
97
  { .out = &attr_dhcp_relay_link_selection, .name = "Relay-Agent-Information.Relay-Link-Selection", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4 },
98
  { .out = &attr_dhcp_subnet_selection_option, .name = "Subnet-Selection-Option", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_dhcpv4 },
99
  { .out = &attr_dhcp_network_subnet, .name = "Network-Subnet", .type = FR_TYPE_IPV4_PREFIX, .dict = &dict_dhcpv4 },
100
  { .out = &attr_dhcp_option_82, .name = "Relay-Agent-Information", .type = FR_TYPE_TLV, .dict = &dict_dhcpv4 },
101
102
  DICT_AUTOLOAD_TERMINATOR
103
};
104
105
/*
106
 *  INADDR_ANY : 68 -> INADDR_BROADCAST : 67  DISCOVER
107
 *  INADDR_BROADCAST : 68 <- SERVER_IP : 67   OFFER
108
 *  INADDR_ANY : 68 -> INADDR_BROADCAST : 67  REQUEST
109
 *  INADDR_BROADCAST : 68 <- SERVER_IP : 67   ACK
110
 */
111
fr_dict_attr_t const **dhcp_header_attrs[] = {
112
  &attr_dhcp_opcode,
113
  &attr_dhcp_hardware_type,
114
  &attr_dhcp_hardware_address_length,
115
  &attr_dhcp_hop_count,
116
  &attr_dhcp_transaction_id,
117
  &attr_dhcp_number_of_seconds,
118
  &attr_dhcp_flags,
119
  &attr_dhcp_client_ip_address,
120
  &attr_dhcp_your_ip_address,
121
  &attr_dhcp_server_ip_address,
122
  &attr_dhcp_gateway_ip_address,
123
  &attr_dhcp_client_hardware_address,
124
  &attr_dhcp_server_host_name,
125
  &attr_dhcp_boot_filename,
126
};
127
size_t dhcp_header_attrs_len = NUM_ELEMENTS(dhcp_header_attrs);
128
129
char const *dhcp_message_types[] = {
130
  "invalid",
131
  "Discover",
132
  "Offer",
133
  "Request",
134
  "Decline",
135
  "Ack",
136
  "NAK",
137
  "Release",
138
  "Inform",
139
  "Force-Renew",
140
  "Lease-Query",
141
  "Lease-Unassigned",
142
  "Lease-Unknown",
143
  "Lease-Active",
144
  "Bulk-Lease-Query",
145
  "Lease-Query-Done"
146
};
147
148
201
#define DHCP_MAX_MESSAGE_TYPE (NUM_ELEMENTS(dhcp_message_types))
149
150
int dhcp_header_sizes[] = {
151
  1,      /* op */
152
  1,      /* htype */
153
  1,      /* hlen */
154
  1,      /* hops */
155
  4,      /* xid */
156
  2,      /* secs */
157
  2,      /* flags */
158
  4,      /* ciaddr */
159
  4,      /* yiaddr */
160
  4,      /* siaddr */
161
  4,      /* giaddr */
162
  DHCP_CHADDR_LEN,  /* chaddr */
163
  DHCP_SNAME_LEN,   /* sname */
164
  DHCP_FILE_LEN   /* file */
165
};
166
167
uint8_t eth_bcast[ETH_ADDR_LEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
168
169
FR_DICT_ATTR_FLAG_FUNC(fr_dhcpv4_attr_flags_t, dns_label)
170
FR_DICT_ATTR_FLAG_FUNC(fr_dhcpv4_attr_flags_t, exists)
171
172
static int dict_flag_prefix(fr_dict_attr_t **da_p, char const *value, UNUSED fr_dict_flag_parser_rule_t const *rules)
173
8
{
174
8
  static fr_table_num_sorted_t const table[] = {
175
8
    { L("bits"),     DHCPV4_FLAG_PREFIX_BITS },
176
8
    { L("split"),      DHCPV4_FLAG_PREFIX_SPLIT }
177
8
  };
178
8
  static size_t table_len = NUM_ELEMENTS(table);
179
180
8
  fr_dhcpv4_attr_flags_t *flags = fr_dict_attr_ext(*da_p, FR_DICT_ATTR_EXT_PROTOCOL_SPECIFIC);
181
8
  fr_dhcpv4_attr_flags_prefix_t flag;
182
183
8
  flag = fr_table_value_by_str(table, value, DHCPV4_FLAG_PREFIX_INVALID);
184
8
  if (flag == DHCPV4_FLAG_PREFIX_INVALID) {
185
0
    fr_strerror_printf("Unknown prefix type '%s'", value);
186
0
    return -1;
187
0
  }
188
8
  flags->prefix = flag;
189
190
8
  return 0;
191
8
}
192
193
static fr_dict_flag_parser_t const dhcpv4_flags[] = {
194
  { L("dns_label"), { .func = dict_flag_dns_label } },
195
  { L("exists"),    { .func = dict_flag_exists } },
196
  { L("prefix"),    { .func = dict_flag_prefix } }
197
};
198
199
/*
200
 *  @todo - arguably we don't want to mutate the input list.
201
 *  Instead, the encoder should just do 3 passes, where middle one
202
 *  ignores the message-type and option 82.
203
 */
204
int8_t fr_dhcpv4_attr_cmp(void const *a, void const *b)
205
0
{
206
0
  fr_pair_t const *my_a = a, *my_b = b;
207
208
0
  PAIR_VERIFY(my_a);
209
0
  PAIR_VERIFY(my_b);
210
211
  /*
212
   *  Message-Type is first, for simplicity.
213
   */
214
0
  if ((my_a->da == attr_dhcp_message_type) && (my_b->da != attr_dhcp_message_type)) return -1;
215
0
  if ((my_a->da != attr_dhcp_message_type) && (my_b->da == attr_dhcp_message_type)) return +1;
216
217
  /*
218
   *  Relay-Agent is last.
219
   *
220
   *  RFC 3046:
221
   *  Servers SHOULD copy the Relay Agent Information
222
     *  option as the last DHCP option in the response.
223
   *
224
   *  Some crazy DHCP relay agents idea of how to strip option 82 in
225
   *  a reply packet is to simply overwrite the 82 with 255 - the
226
   *  "Eod of Options" option - causing the client to then ignore
227
   *  any subsequent options.
228
   *
229
   *  Check if either of the options are option 82
230
   */
231
0
  if ((my_a->da == attr_dhcp_option_82) && (my_b->da != attr_dhcp_option_82)) return +1;
232
0
  if ((my_a->da != attr_dhcp_option_82) && (my_b->da == attr_dhcp_option_82)) return -1;
233
234
0
  return fr_dict_attr_cmp(my_a->da, my_b->da);
235
0
}
236
237
/** Check received DHCP request is valid and build fr_packet_t structure if it is
238
 *
239
 * @param data pointer to received packet.
240
 * @param data_len length of received data, and then length of the actual DHCP data.
241
 * @param[out] message_type where the message type will be stored (if used)
242
 * @param[out] xid where the xid will be stored (if used)
243
 *
244
 * @return
245
 *  - true if the packet is well-formed
246
 *  - false if it's a bad packet
247
 */
248
bool fr_dhcpv4_ok(uint8_t const *data, ssize_t data_len, uint8_t *message_type, uint32_t *xid)
249
1.23k
{
250
1.23k
  uint32_t  magic;
251
1.23k
  uint8_t const *code;
252
1.23k
  size_t    hlen;
253
254
1.23k
  if (data_len < MIN_PACKET_SIZE) {
255
16
    fr_strerror_printf("DHCP packet is too small (%zu < %d)", data_len, MIN_PACKET_SIZE);
256
16
    return false;
257
16
  }
258
259
1.21k
  if (data_len > MAX_PACKET_SIZE) {
260
23
    fr_strerror_printf("DHCP packet is too large (%zd > %d)", data_len, MAX_PACKET_SIZE);
261
23
    return false;
262
23
  }
263
264
1.19k
  if (data[1] != 1) {
265
703
    fr_strerror_printf("DHCP can only process ethernet requests, not type %02x", data[1]);
266
703
    return false;
267
703
  }
268
269
492
  hlen = data[2];
270
492
  if ((hlen != 0) && (hlen != 6)) {
271
6
    fr_strerror_printf("Ethernet HW length incorrect.  Expected 6 got %zu", hlen);
272
6
    return false;
273
6
  }
274
275
486
  memcpy(&magic, data + 236, 4);
276
486
  magic = ntohl(magic);
277
486
  if (magic != DHCP_OPTION_MAGIC_NUMBER) {
278
23
    fr_strerror_const("BOOTP not supported");
279
23
    return false;
280
23
  }
281
282
463
  code = fr_dhcpv4_packet_get_option((dhcp_packet_t const *) data, data_len, attr_dhcp_message_type);
283
463
  if (!code || (code[1] != 1)) {
284
262
    fr_strerror_const("No message-type, or invalid option was found in the packet");
285
262
    return false;
286
262
  }
287
288
201
  if ((code[2] == 0) || (code[2] >= DHCP_MAX_MESSAGE_TYPE)) {
289
1
    fr_strerror_printf("Unknown value %d for message-type option", code[2]);
290
1
    return false;
291
1
  }
292
293
  /*
294
   *  @todo - data_len MAY be larger than the data in the
295
   *  packet.  In which case, we should update data_len with
296
   *  the true size of the packet.
297
   */
298
299
200
  if (message_type) *message_type = code[2];
300
301
200
  if (xid) {
302
0
    memcpy(&magic, data + 4, 4);
303
0
    *xid = ntohl(magic);
304
0
  }
305
306
200
  return true;
307
201
}
308
309
/** Evaluation function for DCHPV4-encodability
310
 *
311
 * @param item  pointer to a fr_pair_t
312
 * @param uctx  context
313
 *
314
 * @return true if the underlying fr_pair_t is DHCPv4 encodable, false otherwise
315
 */
316
bool fr_dhcpv4_is_encodable(void const *item, UNUSED void const *uctx)
317
0
{
318
0
  fr_pair_t const *vp = item;
319
320
0
  PAIR_VERIFY(vp);
321
0
  return (vp->da->dict == dict_dhcpv4) && (!vp->da->flags.internal);
322
0
}
323
324
/** DHCPV4-specific iterator
325
 *
326
 */
327
void *fr_dhcpv4_next_encodable(fr_dcursor_t *cursor, void *current, void *uctx)
328
0
{
329
0
  fr_pair_t *c = current;
330
0
  fr_dict_t *dict = talloc_get_type_abort(uctx, fr_dict_t);
331
332
0
  while ((c = fr_dlist_next(cursor->dlist, c))) {
333
0
    PAIR_VERIFY(c);
334
0
    if (c->da->dict != dict || c->da->flags.internal) continue;
335
336
0
    if (c->vp_type == FR_TYPE_BOOL && fr_dhcpv4_flag_exists(c->da) && !c->vp_bool) continue;
337
338
    /*
339
     *  The VSIO encoder expects to see VENDOR inside of VSA, and has an assertion to that
340
     *  effect.  Until we fix that, we simply ignore all attributes which do not fit into the
341
     *  established hierarchy.
342
     */
343
0
    if (c->da->flags.is_raw && c->da->parent && (c->da->parent->type == FR_TYPE_VSA)) continue;
344
345
0
    fr_assert_msg((c->da->type != FR_TYPE_VENDOR) || (c->da->attr <= 255), "Cursor found unencodable attribute");
346
347
0
    break;
348
0
  }
349
350
0
  return c;
351
0
}
352
353
ssize_t fr_dhcpv4_encode(uint8_t *buffer, size_t buflen, dhcp_packet_t *original, int code, uint32_t xid, fr_pair_list_t *vps)
354
0
{
355
0
  return fr_dhcpv4_encode_dbuff(&FR_DBUFF_TMP(buffer, buflen), original, code, xid, vps);
356
0
}
357
358
ssize_t fr_dhcpv4_encode_dbuff(fr_dbuff_t *dbuff, dhcp_packet_t *original, int code, uint32_t xid, fr_pair_list_t *vps)
359
0
{
360
0
  fr_dcursor_t  cursor;
361
0
  fr_pair_t *vp;
362
0
  ssize_t len;
363
0
  fr_dbuff_t  work_dbuff = FR_DBUFF(dbuff);
364
365
  /*
366
   *  @todo: Make this work again.
367
   */
368
#if 0
369
  mms = DEFAULT_PACKET_SIZE; /* maximum message size */
370
371
  /*
372
   *  Clients can request a LARGER size, but not a
373
   *  smaller one.  They also cannot request a size
374
   *  larger than MTU.
375
   */
376
377
  /* Maximum-Msg-Size */
378
  vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_dhcp_maximum_msg_size);
379
  if (vp && (vp->vp_uint16 > mms)) {
380
    mms = vp->vp_uint16;
381
382
    if (mms > MAX_PACKET_SIZE) mms = MAX_PACKET_SIZE;
383
  }
384
#endif
385
386
0
  vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_opcode);
387
0
  if (vp) {
388
0
    FR_DBUFF_IN_RETURN(&work_dbuff, vp->vp_uint8);
389
0
  } else {
390
0
    FR_DBUFF_IN_RETURN(&work_dbuff, (uint8_t)0x01); /* client message */
391
0
  }
392
393
  /* Hardware-Type */
394
0
  vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_hardware_type);
395
0
  if (vp) {
396
0
    FR_DBUFF_IN_RETURN(&work_dbuff, vp->vp_uint8);
397
398
0
  } else if (original) {
399
0
    FR_DBUFF_IN_RETURN(&work_dbuff, original->htype);
400
401
0
  } else { /* we are ALWAYS ethernet */
402
0
    FR_DBUFF_IN_RETURN(&work_dbuff, (uint8_t)0x01);
403
0
  }
404
405
  /* Hardware-Address-len */
406
0
  vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_hardware_address_length);
407
0
  if (vp) {
408
0
    FR_DBUFF_IN_RETURN(&work_dbuff, vp->vp_uint8);
409
410
0
  } else if (original) {
411
0
    FR_DBUFF_IN_RETURN(&work_dbuff, original->hlen);
412
413
0
  } else { /* we are ALWAYS ethernet */
414
0
    FR_DBUFF_IN_RETURN(&work_dbuff, (uint8_t)0x06);
415
0
  }
416
417
  /* Hop-Count */
418
0
  vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_hop_count);
419
0
  if (vp) {
420
0
    FR_DBUFF_IN_RETURN(&work_dbuff, vp->vp_uint8);
421
422
0
  } else if (original) {
423
0
    FR_DBUFF_IN_RETURN(&work_dbuff, original->hops);
424
425
0
  } else {
426
0
    FR_DBUFF_IN_RETURN(&work_dbuff, (uint8_t)0x00);
427
0
  }
428
429
  /* Transaction-Id */
430
0
  FR_DBUFF_IN_RETURN(&work_dbuff, xid);
431
432
  /* Number-of-Seconds */
433
0
  vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_number_of_seconds);
434
0
  if (vp) {
435
0
    FR_DBUFF_IN_RETURN(&work_dbuff, vp->vp_uint16);
436
0
  } else {
437
0
    FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, sizeof(vp->vp_uint16));
438
0
  }
439
440
  /* Flags */
441
0
  vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_flags);
442
0
  if (vp) {
443
0
    FR_DBUFF_IN_RETURN(&work_dbuff, vp->vp_uint16);
444
0
  } else if (original) { /* Original flags, still in network order */
445
0
    FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t *)&original->flags, sizeof(original->flags));
446
0
  } else {
447
0
    FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, sizeof(vp->vp_uint16));
448
0
  }
449
450
  /* Client-IP-Address */
451
0
  vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_client_ip_address);
452
0
  if (vp) {
453
0
    FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&vp->vp_ipv4addr, sizeof(vp->vp_ipv4addr));
454
0
  } else {
455
0
    FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, sizeof(vp->vp_ipv4addr));
456
0
  }
457
458
  /* Your-IP-address */
459
0
  vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_your_ip_address);
460
0
  if (vp) {
461
0
    FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&vp->vp_ipv4addr, sizeof(vp->vp_ipv4addr));
462
0
  } else {
463
0
    FR_DBUFF_IN_RETURN(&work_dbuff, (uint32_t) INADDR_ANY);
464
0
  }
465
466
  /* Server-IP-Address */
467
0
  vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_server_ip_address);
468
0
  if (vp) {
469
0
    FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&vp->vp_ipv4addr, sizeof(vp->vp_ipv4addr));
470
0
  } else {
471
0
    FR_DBUFF_IN_RETURN(&work_dbuff, (uint32_t) INADDR_ANY);
472
0
  }
473
474
  /*
475
   *  Gateway-IP-Address
476
   */
477
0
  vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_gateway_ip_address);
478
0
  if (vp) {
479
0
    FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&vp->vp_ipv4addr, sizeof(vp->vp_ipv4addr));
480
481
0
  } else if (original) { /* copy whatever value was in the original */
482
0
    FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)&original->giaddr, sizeof(original->giaddr));
483
484
0
  } else {
485
0
    FR_DBUFF_IN_RETURN(&work_dbuff, (uint32_t) INADDR_ANY);
486
0
  }
487
488
  /* Client-Hardware-Address */
489
0
  if ((vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_client_hardware_address))) {
490
0
    FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, (uint8_t const *)vp->vp_ether, sizeof(vp->vp_ether));
491
0
    FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, DHCP_CHADDR_LEN - sizeof(vp->vp_ether));
492
493
0
  } else if (original) { /* copy whatever value was in the original */
494
0
    FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, &original->chaddr[0], sizeof(original->chaddr));
495
496
0
  } else {
497
0
    FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, DHCP_CHADDR_LEN);
498
0
  }
499
500
  /* Server-Host-Name */
501
0
  if ((vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_server_host_name))) {
502
0
    if (vp->vp_length > DHCP_SNAME_LEN) {
503
0
      FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_strvalue, DHCP_SNAME_LEN);
504
0
    } else {
505
0
      FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_strvalue, vp->vp_length);
506
0
      FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, DHCP_SNAME_LEN - vp->vp_length);
507
0
    }
508
0
  } else {
509
0
    FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, DHCP_SNAME_LEN);
510
0
  }
511
512
  /*
513
   *  Copy over Boot-Filename.
514
   *
515
   *  FIXME: This copy should be delayed until AFTER the options
516
   *  have been processed.  If there are too many options for
517
   *  the packet, then they go into the sname && filename fields.
518
   *  When that happens, the boot filename is passed as an option,
519
   *  instead of being placed verbatim in the filename field.
520
   */
521
522
  /* Boot-Filename */
523
0
  if ((vp = fr_pair_find_by_da(vps, NULL, attr_dhcp_boot_filename))) {
524
0
    if (vp->vp_length > DHCP_FILE_LEN) {
525
0
      FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_strvalue, DHCP_FILE_LEN);
526
0
    } else {
527
0
      FR_DBUFF_IN_MEMCPY_RETURN(&work_dbuff, vp->vp_strvalue, vp->vp_length);
528
0
      FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, DHCP_FILE_LEN - vp->vp_length);
529
0
    }
530
0
  } else {
531
0
    FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, DHCP_FILE_LEN);
532
0
  }
533
534
  /* DHCP magic number */
535
0
  FR_DBUFF_IN_RETURN(&work_dbuff, (uint32_t) DHCP_OPTION_MAGIC_NUMBER);
536
537
  /*
538
   *  Pre-sort attributes into contiguous blocks so that fr_dhcpv4_encode_option
539
   *  operates correctly. This changes the order of the list, but never mind...
540
   *
541
   *  If attr_dhcp_message_type is present it will have been sorted as the first
542
   *  option, so we don't need to search for it.
543
   */
544
0
  fr_pair_list_sort(vps, fr_dhcpv4_attr_cmp);
545
0
  fr_pair_dcursor_iter_init(&cursor, vps, fr_dhcpv4_next_encodable, dict_dhcpv4);
546
547
0
  vp = fr_dcursor_head(&cursor);
548
0
  if (vp && vp->da == attr_dhcp_message_type) {
549
0
    FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, FR_MESSAGE_TYPE, 0x01, vp->vp_uint8);
550
0
    fr_dcursor_next(&cursor); /* Skip message type so it doesn't get double encoded */
551
0
  } else {
552
0
    FR_DBUFF_IN_BYTES_RETURN(&work_dbuff, FR_MESSAGE_TYPE, 0x01, (uint8_t)code);
553
0
  }
554
555
  /*
556
   *  Each call to fr_dhcpv4_encode_option will encode one complete DHCP option,
557
   *  and sub options.
558
   */
559
0
  while ((vp = fr_dcursor_current(&cursor))) {
560
    /*
561
     *  The encoder skips message type, and returns
562
     *  "len==0" for it.  We want to allow that, BUT
563
     *  stop when the encoder returns "len==0" for
564
     *  other attributes.  So we need to skip it
565
     *  manually, too.
566
     */
567
0
    if (vp->da == attr_dhcp_message_type) {
568
0
      (void) fr_dcursor_next(&cursor);
569
0
      continue;
570
0
    }
571
572
0
    len = fr_dhcpv4_encode_option(&work_dbuff,
573
0
                &cursor, &(fr_dhcpv4_ctx_t){ .root = fr_dict_root(dict_dhcpv4) });
574
0
    if (len <= 0) break;
575
0
  }
576
577
0
  FR_DBUFF_IN_RETURN(&work_dbuff, (uint8_t)FR_END_OF_OPTIONS);
578
579
  /*
580
   *  FIXME: if (fr_dbuff_used(&work_dbuff) > mms),
581
   *    then we put the extra options into the "sname" and "file"
582
   *    fields, AND set the "end option option" in the "options"
583
   *    field.  We also set the "overload option",
584
   *    and put options into the "file" field, followed by
585
   *    the "sname" field.  Where each option is completely
586
   *    enclosed in the "file" and/or "sname" field, AND
587
   *    followed by the "end of option", and MUST be followed
588
   *    by padding option.
589
   *
590
   *  Yuck.  That sucks...
591
   */
592
0
  if (fr_dbuff_used(&work_dbuff) < DEFAULT_PACKET_SIZE) {
593
0
    FR_DBUFF_MEMSET_RETURN(&work_dbuff, 0, DEFAULT_PACKET_SIZE - fr_dbuff_used(&work_dbuff));
594
0
  }
595
596
0
  return fr_dbuff_set(dbuff, fr_dbuff_used(&work_dbuff));
597
0
}
598
599
600
/** Resolve/cache attributes in the DHCP dictionary
601
 *
602
 * @return
603
 *  - 0 on success.
604
 *  - -1 on failure.
605
 */
606
int fr_dhcpv4_global_init(void)
607
6
{
608
6
  if (instance_count > 0) {
609
2
    instance_count++;
610
2
    return 0;
611
2
  }
612
613
4
  instance_count++;
614
615
4
  if (fr_dict_autoload(dhcpv4_dict) < 0) {
616
0
  fail:
617
0
    instance_count--;
618
0
    return -1;
619
0
  }
620
621
4
  if (fr_dict_attr_autoload(dhcpv4_dict_attr) < 0) {
622
0
    fr_dict_autofree(dhcpv4_dict);
623
0
    goto fail;
624
0
  }
625
626
4
  instantiated = true;
627
4
  return 0;
628
4
}
629
630
void fr_dhcpv4_global_free(void)
631
4
{
632
4
  if (!instantiated) return;
633
634
4
  fr_assert(instance_count > 0);
635
636
4
  if (--instance_count > 0) return;
637
638
2
  fr_dict_autofree(dhcpv4_dict);
639
2
  instantiated = false;
640
2
}
641
642
643
static char const *short_header_names[] = {
644
  "opcode",
645
  "hwtype",
646
  "hwaddrlen",
647
  "hop_count",
648
  "xid",
649
  "seconds",
650
  "flags",
651
  "ciaddr",
652
  "yiaddr",
653
  "siaddr",
654
  "giaddr",
655
  "chaddr",
656
  "server_hostname",
657
  "boot_filename",
658
};
659
660
static void print_hex_data(FILE *fp, uint8_t const *ptr, int attrlen, int depth)
661
0
{
662
0
  int i;
663
0
  static char const tabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
664
665
0
  for (i = 0; i < attrlen; i++) {
666
0
    if ((i > 0) && ((i & 0x0f) == 0x00))
667
0
      fprintf(fp, "%.*s", depth, tabs);
668
0
    fprintf(fp, "%02x ", ptr[i]);
669
0
    if ((i & 0x0f) == 0x0f) fprintf(fp, "\n");
670
0
  }
671
0
  if ((i & 0x0f) != 0) fprintf(fp, "\n");
672
0
}
673
674
/** Print a raw DHCP packet as hex.
675
 *
676
 */
677
void fr_dhcpv4_print_hex(FILE *fp, uint8_t const *packet, size_t packet_len)
678
0
{
679
0
  int i;
680
0
  uint8_t const *attr, *end;
681
682
0
  end = packet + packet_len;
683
0
  attr = packet;
684
685
0
  for (i = 0; i < 14; i++) {
686
0
    fprintf(fp, "\t%s: ", short_header_names[i]);
687
0
    print_hex_data(fp, attr, dhcp_header_sizes[i], 2);
688
0
    attr += dhcp_header_sizes[i];
689
0
  }
690
691
0
  fprintf(fp, "\tmagic:\t%02x %02x %02x %02x\n", attr[0], attr[1], attr[2], attr[3]);
692
0
  attr += 4;
693
694
0
  fprintf(fp, "\toptions\n");
695
0
  while (attr < end) {
696
0
    fprintf(fp, "\t\t");
697
698
    /*
699
     *  The caller should already have called fr_dhcpv4_ok().
700
     */
701
0
    fr_assert((attr + 2) <= end);
702
703
    /*
704
     *  End of options.
705
     */
706
0
    if ((attr[0] == 0) || (attr[1] == 255)) {
707
0
      fprintf(fp, "%02x\n", attr[0]);
708
0
      break;
709
0
    }
710
711
0
    fprintf(fp, "%02x  %02x  ", attr[0], attr[1]);
712
713
0
    print_hex_data(fp, attr + 2, attr[1], 3);
714
715
0
    attr += attr[1] + 2;
716
0
  }
717
718
0
  fprintf(fp, "\n");
719
0
}
720
721
static bool attr_valid(fr_dict_attr_t *da)
722
1.62k
{
723
  /*
724
   *  DNS labels are strings, but are known width.
725
   */
726
1.62k
  if (fr_dhcpv4_flag_dns_label(da)) {
727
40
    if (da->type != FR_TYPE_STRING) {
728
0
      fr_strerror_const("The 'dns_label' flag can only be used with attributes of type 'string'");
729
0
      return false;
730
0
    }
731
732
40
    da->flags.is_known_width = true;
733
40
    da->flags.length = 0;
734
40
  }
735
736
1.62k
  if (da->type == FR_TYPE_ATTR)  {
737
4
    da->flags.is_known_width = true;
738
4
    da->flags.length = 1;
739
4
  }
740
741
1.62k
  if (da_is_length_field16(da)) {
742
0
    fr_strerror_const("The 'length=uint16' flag cannot be used for DHCPv4");
743
0
    return false;
744
0
  }
745
746
  /*
747
   *  "arrays" of string/octets are encoded as a 8-bit
748
   *  length, followed by the actual data.
749
   */
750
1.62k
  if (da->flags.array) {
751
228
    if ((da->type == FR_TYPE_STRING) || (da->type == FR_TYPE_OCTETS)) {
752
32
      if (da->flags.extra && !da_is_length_field8(da)) {
753
0
        fr_strerror_const("Invalid flags");
754
0
        return false;
755
0
      }
756
757
32
      da->flags.is_known_width = true;
758
32
      da->flags.extra = true;
759
32
      da->flags.subtype = FLAG_LENGTH_UINT8;
760
32
    }
761
762
228
    if (!da->flags.is_known_width) {
763
0
      fr_strerror_const("DHCPv4 arrays require data types which have known width");
764
0
      return false;
765
0
    }
766
228
  }
767
768
  /*
769
   *  "extra" signifies that subtype is being used by the
770
   *  dictionaries itself.
771
   */
772
1.62k
  if (da->flags.extra || !da->flags.subtype) return true;
773
774
0
  if ((da->type != FR_TYPE_IPV4_PREFIX) &&
775
0
      (fr_dhcpv4_flag_prefix(da))) {
776
0
    fr_strerror_const("The 'prefix=...' flag can only be used with attributes of type 'ipv4prefix'");
777
0
    return false;
778
0
  }
779
780
0
  if ((da->type != FR_TYPE_BOOL) && fr_dhcpv4_flag_exists(da)) {
781
0
    fr_strerror_const("The 'exists' flag can only be used with attributes of type 'bool'");
782
0
    return false;
783
0
  }
784
785
0
  if ((da->type == FR_TYPE_ATTR) && !da->parent->flags.is_root) {
786
0
    fr_strerror_const("The 'attribute' data type can only be used at the dictionary root");
787
0
    return false;
788
0
  }
789
790
0
  return true;
791
0
}
792
793
extern fr_dict_protocol_t libfreeradius_dhcpv4_dict_protocol;
794
fr_dict_protocol_t libfreeradius_dhcpv4_dict_protocol = {
795
  .name = "dhcpv4",
796
  .default_type_size = 1,
797
  .default_type_length = 1,
798
  .attr = {
799
    .flags = {
800
      .table = dhcpv4_flags,
801
      .table_len = NUM_ELEMENTS(dhcpv4_flags),
802
      .len = sizeof(fr_dhcpv4_attr_flags_t)
803
    },
804
    .valid = attr_valid
805
  },
806
807
  .init = fr_dhcpv4_global_init,
808
  .free = fr_dhcpv4_global_free,
809
810
  .encode   = fr_dhcpv4_encode_foreign,
811
  .decode   = fr_dhcpv4_decode_foreign,
812
};