Coverage Report

Created: 2026-01-10 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/protocols/dhcpv6/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: 6b104bedba0e57849d9e5f3d32cc128d9e2ca3ad $
19
 *
20
 * @file protocols/dhcpv6/base.c
21
 * @brief Functions to encode DHCP options.
22
 *
23
 * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org)
24
 *
25
 * @copyright 2018 The FreeRADIUS server project
26
 * @copyright 2018 NetworkRADIUS SARL (legal@networkradius.com)
27
 */
28
#include <freeradius-devel/io/pair.h>
29
#include <freeradius-devel/protocol/dhcpv6/freeradius.internal.h>
30
#include <freeradius-devel/protocol/dhcpv6/rfc3315.h>
31
#include <freeradius-devel/protocol/dhcpv6/rfc5007.h>
32
#include <freeradius-devel/util/proto.h>
33
#include <freeradius-devel/util/rand.h>
34
35
#include "dhcpv6.h"
36
#include "attrs.h"
37
38
static uint32_t instance_count = 0;
39
static bool instantiated = false;
40
41
fr_dict_t const *dict_dhcpv6;
42
43
extern fr_dict_autoload_t libfreeradius_dhcpv6_dict[];
44
fr_dict_autoload_t libfreeradius_dhcpv6_dict[] = {
45
  { .out = &dict_dhcpv6, .proto = "dhcpv6" },
46
  DICT_AUTOLOAD_TERMINATOR
47
};
48
49
fr_dict_attr_t const *attr_packet_type;
50
fr_dict_attr_t const *attr_transaction_id;
51
fr_dict_attr_t const *attr_hop_count;
52
fr_dict_attr_t const *attr_relay_link_address;
53
fr_dict_attr_t const *attr_relay_peer_address;
54
fr_dict_attr_t const *attr_relay_message;
55
fr_dict_attr_t const *attr_option_request;
56
57
extern fr_dict_attr_autoload_t libfreeradius_dhcpv6_dict_attr[];
58
fr_dict_attr_autoload_t libfreeradius_dhcpv6_dict_attr[] = {
59
  { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_dhcpv6 },
60
  { .out = &attr_transaction_id, .name = "Transaction-Id", .type = FR_TYPE_OCTETS, .dict = &dict_dhcpv6 },
61
  { .out = &attr_hop_count, .name = "Hop-Count", .type = FR_TYPE_UINT8, .dict = &dict_dhcpv6 },
62
  { .out = &attr_relay_link_address, .name = "Relay-Link-Address", .type = FR_TYPE_IPV6_ADDR, .dict = &dict_dhcpv6 },
63
  { .out = &attr_relay_peer_address, .name = "Relay-Peer-Address", .type = FR_TYPE_IPV6_ADDR, .dict = &dict_dhcpv6 },
64
  { .out = &attr_relay_message, .name = "Relay-Message", .type = FR_TYPE_GROUP, .dict = &dict_dhcpv6 },
65
  { .out = &attr_option_request, .name = "Option-Request", .type = FR_TYPE_ATTR, .dict = &dict_dhcpv6 },
66
  DICT_AUTOLOAD_TERMINATOR
67
};
68
69
/*
70
 * grep VALUE share/dictionary/dhcpv6/dictionary.freeradius.internal  | awk '{print "[" $4 "] = \"" $3 "\"," }'
71
 */
72
char const *fr_dhcpv6_packet_names[FR_DHCPV6_CODE_MAX] = {
73
   [0]            = "invalid",
74
   [FR_PACKET_TYPE_VALUE_SOLICIT]     = "Solicit",
75
   [FR_PACKET_TYPE_VALUE_ADVERTISE]   = "Advertise",
76
   [FR_PACKET_TYPE_VALUE_REQUEST]     = "Request",
77
   [FR_PACKET_TYPE_VALUE_CONFIRM]     = "Confirm",
78
   [FR_PACKET_TYPE_VALUE_RENEW]     = "Renew",
79
   [FR_PACKET_TYPE_VALUE_REBIND]      = "Rebind",
80
   [FR_PACKET_TYPE_VALUE_REPLY]     = "Reply",
81
   [FR_PACKET_TYPE_VALUE_RELEASE]     = "Release",
82
   [FR_PACKET_TYPE_VALUE_DECLINE]     = "Decline",
83
   [FR_PACKET_TYPE_VALUE_RECONFIGURE]   = "Reconfigure",
84
   [FR_PACKET_TYPE_VALUE_INFORMATION_REQUEST] = "Information-Request",
85
   [FR_PACKET_TYPE_VALUE_RELAY_FORWARD]   = "Relay-Forward",
86
   [FR_PACKET_TYPE_VALUE_RELAY_REPLY]   = "Relay-Reply",
87
   [FR_PACKET_TYPE_VALUE_LEASE_QUERY]   = "Lease-Query",
88
   [FR_PACKET_TYPE_VALUE_LEASE_QUERY_REPLY] = "Lease-Query-Reply",
89
   [FR_PACKET_TYPE_VALUE_LEASE_QUERY_DONE]  = "Lease-Query-Done",
90
   [FR_PACKET_TYPE_VALUE_LEASE_QUERY_DATA]  = "Lease-Query-Data",
91
   [FR_PACKET_TYPE_VALUE_RECONFIGURE_REQUEST] = "Reconfigure-Request",
92
   [FR_PACKET_TYPE_VALUE_RECONFIGURE_REPLY] = "Reconfigure-Reply",
93
   [FR_PACKET_TYPE_VALUE_DHCPV4_QUERY]    = "DHCPv4-Query",
94
   [FR_PACKET_TYPE_VALUE_DHCPV4_RESPONSE]   = "DHCPv4-Response",
95
   [FR_PACKET_TYPE_VALUE_ACTIVE_LEASE_QUERY]  = "Active-Lease-Query",
96
   [FR_PACKET_TYPE_VALUE_START_TLS]   = "Start-TLS",
97
   [FR_PACKET_TYPE_VALUE_BIND_UPDATE]   = "Bind-Update",
98
   [FR_PACKET_TYPE_VALUE_BIND_REPLY]    = "Bind-Reply",
99
   [FR_PACKET_TYPE_VALUE_POOL_REQUEST]    = "Pool-Request",
100
   [FR_PACKET_TYPE_VALUE_POOL_RESPONSE]   = "Pool-Response",
101
   [FR_PACKET_TYPE_VALUE_UPDATE_REQUEST]    = "Update-Request",
102
   [FR_PACKET_TYPE_VALUE_UPDATE_REQUEST_ALL]  = "Update-Request-All",
103
   [FR_PACKET_TYPE_VALUE_UPDATE_DONE]   = "Update-Done",
104
   [FR_PACKET_TYPE_VALUE_CONNECT]     = "Connect",
105
   [FR_PACKET_TYPE_VALUE_CONNECT_REPLY]   = "Connect-Reply",
106
   [FR_PACKET_TYPE_VALUE_DISCONNECT]    = "Disconnect",
107
   [FR_PACKET_TYPE_VALUE_STATE]     = "State",
108
   [FR_PACKET_TYPE_VALUE_CONTACT]     = "Contact"
109
};
110
111
FR_DICT_ATTR_FLAG_FUNC(fr_dhcpv6_attr_flags_t, dns_label)
112
FR_DICT_ATTR_FLAG_FUNC(fr_dhcpv6_attr_flags_t, partial_dns_label)
113
114
static fr_dict_flag_parser_t const dhcpv6_flags[] = {
115
  { L("dns_label"),   { .func = dict_flag_dns_label } },
116
  { L("partial_dns_label"), { .func = dict_flag_partial_dns_label } }
117
};
118
119
static ssize_t fr_dhcpv6_ok_internal(uint8_t const *packet, uint8_t const *end, size_t max_attributes, int depth);
120
121
static ssize_t fr_dhcpv6_options_ok(uint8_t const *packet, uint8_t const *end, size_t max_attributes,
122
            bool allow_relay, int depth)
123
0
{
124
0
  size_t attributes;
125
0
  uint8_t const *p;
126
127
0
  attributes = 0;
128
0
  p = packet;
129
130
0
  while (p < end) {
131
0
    uint16_t len;
132
133
0
    if ((size_t)(end - p) < DHCPV6_OPT_HDR_LEN) {
134
0
      fr_strerror_const("Not enough room for option header");
135
0
      return -(p - packet);
136
0
    }
137
138
0
    len = DHCPV6_GET_OPTION_LEN(p);
139
0
    if ((size_t)(end - p) < (DHCPV6_OPT_HDR_LEN + len)) {
140
0
      fr_strerror_const("Option length overflows the packet");
141
0
      return -(p - packet);
142
0
    }
143
144
0
    attributes++;
145
0
    if (attributes > (size_t) max_attributes) {
146
0
      fr_strerror_const("Too many attributes");
147
0
      return -(p - packet);
148
0
    }
149
150
    /*
151
     *  Recurse into the Relay-Message attribute, but
152
     *  only if the outer packet was a relayed message.
153
     */
154
0
    if (allow_relay && (p[0] == 0) && (p[1] == attr_relay_message->attr)) {
155
0
      ssize_t child;
156
157
      /*
158
       *  Recurse to check the encapsulated packet.
159
       */
160
0
      child = fr_dhcpv6_ok_internal(p + 4, p + 4 + len, max_attributes - attributes, depth + 1);
161
0
      if (child <= 0) return -((p + 4) - packet) + child;
162
163
0
      attributes += child;
164
0
    }
165
166
0
    p += DHCPV6_OPT_HDR_LEN + len;
167
0
  }
168
169
0
  return attributes;
170
0
}
171
172
static ssize_t fr_dhcpv6_ok_internal(uint8_t const *packet, uint8_t const *end, size_t max_attributes, int depth)
173
0
{
174
0
  uint8_t const *p;
175
0
  ssize_t   attributes;
176
0
  bool    allow_relay;
177
0
  size_t    packet_len = end - packet;
178
179
0
  if (end == packet) {
180
0
    fr_strerror_const("Packet is empty");
181
0
    return 0;
182
0
  }
183
184
0
  if (depth > DHCPV6_MAX_RELAY_NESTING) {
185
0
    fr_strerror_const("Too many layers forwarded packets");
186
0
    return 0;
187
0
  }
188
189
0
  switch (packet[0]) {
190
0
  case FR_DHCPV6_RELAY_FORWARD:
191
0
  case FR_DHCPV6_RELAY_REPLY:
192
0
    if (packet_len < DHCPV6_RELAY_HDR_LEN) {
193
0
      fr_strerror_const("Packet is too small for relay header");
194
0
      return 0;
195
0
    }
196
197
0
    p = packet + DHCPV6_RELAY_HDR_LEN;
198
0
    allow_relay = true;
199
0
    break;
200
201
0
  default:
202
    /*
203
     *  8 bit code + 24 bits of transaction ID
204
     */
205
0
    if (packet_len < DHCPV6_HDR_LEN) {
206
0
      fr_strerror_const("Packet is too small for DHCPv6 header");
207
0
      return 0;
208
0
    }
209
210
0
    p = packet + DHCPV6_HDR_LEN;
211
0
    allow_relay = false;
212
0
    break;
213
0
  }
214
215
0
  attributes = fr_dhcpv6_options_ok(p, end, max_attributes, allow_relay, depth);
216
0
  if (attributes < 0) return -(p - packet) + attributes;
217
218
0
  return attributes;
219
0
}
220
221
222
/** See if the data pointed to by PTR is a valid DHCPv6 packet.
223
 *
224
 * @param[in] packet    to check.
225
 * @param[in] packet_len  The size of the packet data.
226
 * @param[in] max_attributes  to allow in the packet.
227
 * @return
228
 *  - True on success.
229
 *  - False on failure.
230
 */
231
bool fr_dhcpv6_ok(uint8_t const *packet, size_t packet_len, uint32_t max_attributes)
232
0
{
233
0
  ssize_t slen;
234
235
0
  slen = fr_dhcpv6_ok_internal(packet, packet + packet_len, max_attributes, 0);
236
0
  if (slen <= 0) {
237
0
    fr_strerror_printf_push("Invalid DHCPv6 packet starting at offset %zd", -slen);
238
0
    return false;
239
0
  }
240
241
0
  return true;
242
0
}
243
244
/*
245
 *  Return pointer to a particular option.
246
 */
247
uint8_t const *fr_dhcpv6_option_find(uint8_t const *start, uint8_t const *end, unsigned int option)
248
0
{
249
0
  uint8_t const *p = start;
250
251
0
  while (p < end) {
252
0
    uint16_t found;
253
0
    uint16_t len;
254
255
0
    if ((size_t)(end - p) < DHCPV6_OPT_HDR_LEN) return NULL;
256
257
0
    found = DHCPV6_GET_OPTION_NUM(p);
258
0
    len = DHCPV6_GET_OPTION_LEN(p);
259
260
0
    if ((p + DHCPV6_OPT_HDR_LEN + len) > end) return NULL;
261
262
0
    if (found == option) return p;
263
264
0
    p += DHCPV6_OPT_HDR_LEN + len;
265
0
  }
266
267
0
  return NULL;
268
0
}
269
270
static bool duid_match(uint8_t const *option, fr_dhcpv6_decode_ctx_t const *packet_ctx)
271
0
{
272
0
  uint16_t len;
273
274
0
  len = DHCPV6_GET_OPTION_LEN(option);
275
0
  if (len != packet_ctx->duid_len) return false;
276
0
  if (memcmp(option + 4, packet_ctx->duid, packet_ctx->duid_len) != 0) return false;
277
278
0
  return true;
279
0
}
280
281
/** Verify a reply packet from a server to a client
282
 *
283
 */
284
static bool verify_to_client(uint8_t const *packet, size_t packet_len, fr_dhcpv6_decode_ctx_t const *packet_ctx)
285
0
{
286
0
  uint32_t transaction_id;
287
0
  uint8_t const *option;
288
0
  uint8_t const *options = packet + 4;
289
0
  uint8_t const *end = packet + packet_len;
290
291
0
  switch (packet[0]) {
292
0
  case FR_PACKET_TYPE_VALUE_ADVERTISE:
293
0
    transaction_id = fr_nbo_to_uint24(&packet[1]);
294
0
    if (transaction_id != packet_ctx->transaction_id) {
295
0
    fail_tid:
296
0
      fr_strerror_const("Transaction ID does not match");
297
0
      return false;
298
0
    }
299
300
0
    if (!fr_dhcpv6_option_find(options, end, FR_SERVER_ID)) {
301
0
    fail_sid:
302
0
      fr_strerror_const("Packet does not contain a Server-Id option");
303
0
      return false;
304
0
    }
305
306
0
    option = fr_dhcpv6_option_find(options, end, FR_CLIENT_ID);
307
0
    if (!option) {
308
0
    fail_cid:
309
0
      fr_strerror_const("Packet does not contain a Client-Id option");
310
0
      return false;
311
0
    }
312
313
    /*
314
     *  The DUID MUST exist.
315
     */
316
0
    if (!packet_ctx->duid) {
317
0
    fail_duid:
318
0
      fr_strerror_const("Packet context does not contain a DUID");
319
0
      return false;
320
0
    }
321
322
0
  check_duid:
323
0
    if (!duid_match(option, packet_ctx)) {
324
0
    fail_match:
325
0
      fr_strerror_const("DUID in packet does not match our DUID");
326
0
      return false;
327
0
    }
328
0
    return true;
329
330
0
  case FR_PACKET_TYPE_VALUE_REPLY:
331
0
    transaction_id = fr_nbo_to_uint24(&packet[1]);
332
0
    if (transaction_id != packet_ctx->transaction_id) goto fail_tid;
333
334
0
    if (!fr_dhcpv6_option_find(options, end, FR_SERVER_ID)) goto fail_sid;
335
336
    /*
337
     *  It's OK to not have a client ID in the reply if we didn't send one.
338
     */
339
0
    option = fr_dhcpv6_option_find(options, end, FR_CLIENT_ID);
340
0
    if (!option) {
341
0
      if (!packet_ctx->duid) return true;
342
0
      goto fail_cid;
343
0
    }
344
0
    goto check_duid;
345
346
0
  case FR_PACKET_TYPE_VALUE_RECONFIGURE:
347
0
    if (!fr_dhcpv6_option_find(options, end, FR_SERVER_ID)) goto fail_sid;
348
349
0
    option = fr_dhcpv6_option_find(options, end, FR_CLIENT_ID);
350
0
    if (!option) goto fail_cid;
351
352
    /*
353
     *  The DUID MUST exist.
354
     */
355
0
    if (!packet_ctx->duid) goto fail_duid;
356
0
    if (!duid_match(option, packet_ctx)) goto fail_match;
357
358
0
    option = fr_dhcpv6_option_find(options, end, FR_RECONF_MSG);
359
0
    if (!option) {
360
0
      fr_strerror_const("Packet does not contain a Reconf-Msg option");
361
0
      return false;
362
0
    }
363
364
    /*
365
     *  @todo - check reconfigure message type, and
366
     *  reject if it doesn't match.
367
     */
368
369
    /*
370
     *  @todo - check for authentication option and
371
     *  verify it.
372
     */
373
0
    break;
374
375
0
  case FR_DHCPV6_RELAY_REPLY:
376
0
    if (packet_len < DHCPV6_RELAY_HDR_LEN) {
377
0
      fr_strerror_const("Relay-Reply message is too small");
378
0
      return false;
379
0
    }
380
381
0
    options += (DHCPV6_RELAY_HDR_LEN - 4); /* we assumed it was a normal packet above  */
382
0
    option = fr_dhcpv6_option_find(options, end, FR_RELAY_MESSAGE);
383
0
    if (!option) {
384
0
      fr_strerror_const("Packet does not contain a Relay-Message option");
385
0
      return false;
386
0
    }
387
0
    return verify_to_client(option + 4, DHCPV6_GET_OPTION_LEN(option), packet_ctx);
388
389
0
  case FR_DHCPV6_LEASE_QUERY_REPLY:
390
0
    transaction_id = fr_nbo_to_uint24(&packet[1]);
391
0
    if (transaction_id != packet_ctx->transaction_id) goto fail_tid;
392
393
0
    if (!fr_dhcpv6_option_find(options, end, FR_SERVER_ID)) goto fail_sid;
394
395
0
    option = fr_dhcpv6_option_find(options, end, FR_CLIENT_ID);
396
0
    if (!option) goto fail_cid;
397
398
    /*
399
     *  The DUID MUST exist.
400
     */
401
0
    if (!packet_ctx->duid) goto fail_duid;
402
0
    if (!duid_match(option, packet_ctx)) goto fail_match;
403
0
    break;
404
405
0
  case FR_PACKET_TYPE_VALUE_REQUEST:
406
0
  case FR_PACKET_TYPE_VALUE_CONFIRM:
407
0
  case FR_PACKET_TYPE_VALUE_RENEW:
408
0
  case FR_PACKET_TYPE_VALUE_REBIND:
409
0
  case FR_PACKET_TYPE_VALUE_RELEASE:
410
0
  case FR_PACKET_TYPE_VALUE_DECLINE:
411
0
  case FR_PACKET_TYPE_VALUE_INFORMATION_REQUEST:
412
0
  default:
413
0
    fr_strerror_const("Invalid message type sent to client");
414
0
    return false;
415
0
  }
416
417
0
  return true;
418
0
}
419
420
421
/** Verify a packet from a client to a server
422
 *
423
 */
424
static bool verify_from_client(uint8_t const *packet, size_t packet_len, fr_dhcpv6_decode_ctx_t const *packet_ctx)
425
0
{
426
0
  uint8_t const *option;
427
0
  uint8_t const *options = packet + 4;
428
0
  uint8_t const *end = packet + packet_len;
429
430
  /*
431
   *  Servers MUST have a DUID
432
   */
433
0
  if (!packet_ctx->duid) {
434
0
    fr_strerror_const("Packet context does not contain a DUID");
435
0
    return false;
436
0
  }
437
438
0
  switch (packet[0]) {
439
0
  case FR_PACKET_TYPE_VALUE_SOLICIT:
440
0
  case FR_PACKET_TYPE_VALUE_CONFIRM:
441
0
  case FR_PACKET_TYPE_VALUE_REBIND:
442
0
    if (!fr_dhcpv6_option_find(options, end, FR_CLIENT_ID)) {
443
0
    fail_cid:
444
0
      fr_strerror_const("Packet does not contain a Client-Id option");
445
0
      return false;
446
0
    }
447
448
0
    if (!fr_dhcpv6_option_find(options, end, FR_SERVER_ID)) {
449
0
    fail_sid:
450
0
      fr_strerror_const("Packet does not contain a Server-Id option");
451
0
      return false;
452
0
    }
453
0
    break;
454
455
0
  case FR_PACKET_TYPE_VALUE_REQUEST:
456
0
  case FR_PACKET_TYPE_VALUE_RENEW:
457
0
  case FR_PACKET_TYPE_VALUE_DECLINE:
458
0
  case FR_PACKET_TYPE_VALUE_RELEASE:
459
0
    if (!fr_dhcpv6_option_find(options, end, FR_CLIENT_ID)) goto fail_cid;
460
461
0
    option = fr_dhcpv6_option_find(options, end, FR_SERVER_ID);
462
0
    if (!option) goto fail_sid;
463
464
0
    if (!duid_match(option, packet_ctx)) {
465
0
    fail_match:
466
0
      fr_strerror_const("DUID in packet does not match our DUID");
467
0
      return false;
468
0
    }
469
0
    break;
470
471
0
  case FR_PACKET_TYPE_VALUE_INFORMATION_REQUEST:
472
0
    option = fr_dhcpv6_option_find(options, end, FR_SERVER_ID);
473
0
    if (!option) goto fail_sid;
474
475
0
    if (!duid_match(option, packet_ctx)) goto fail_match;
476
477
    /*
478
     *  IA options are forbidden.
479
     */
480
0
    if (fr_dhcpv6_option_find(options, end, FR_IA_NA)) {
481
0
      fr_strerror_const("Packet contains an IA-NA option");
482
0
      return false;
483
0
    }
484
0
    if (fr_dhcpv6_option_find(options, end, FR_IA_TA)) {
485
0
      fr_strerror_const("Packet contains an IA-TA option");
486
0
      return false;
487
0
    }
488
0
    if (fr_dhcpv6_option_find(options, end, FR_IA_ADDR)) {
489
0
      fr_strerror_const("Packet contains an IA-Addr option");
490
0
      return false;
491
0
    }
492
0
    break;
493
494
0
  case FR_DHCPV6_RELAY_FORWARD:
495
0
    if (packet_len < DHCPV6_RELAY_HDR_LEN) {
496
0
      fr_strerror_const("Relay-Forward message is too small");
497
0
      return false;
498
0
    }
499
500
0
    options += (DHCPV6_RELAY_HDR_LEN - 4); /* we assumed it was a normal packet above  */
501
0
    option = fr_dhcpv6_option_find(options, end, FR_RELAY_MESSAGE);
502
0
    if (!option) {
503
0
      fr_strerror_const("Packet does not contain a Relay-Message option");
504
0
      return false;
505
0
    }
506
507
0
    return verify_from_client(option + 4, DHCPV6_GET_OPTION_LEN(option), packet_ctx);
508
509
0
  case FR_PACKET_TYPE_VALUE_LEASE_QUERY:
510
0
    if (!fr_dhcpv6_option_find(options, end, FR_CLIENT_ID)) goto fail_cid;
511
512
    /*
513
     *  Server-ID is a SHOULD, but if it exists, it
514
     *  MUST match.
515
     */
516
0
    option = fr_dhcpv6_option_find(options, end, FR_SERVER_ID);
517
0
    if (option && !duid_match(option, packet_ctx)) goto fail_match;
518
519
0
    option = fr_dhcpv6_option_find(options, end, FR_LEASE_QUERY);
520
0
    if (!option) {
521
0
      fr_strerror_const("Packet does not contain a Lease-Query option");
522
0
      return false;
523
0
    }
524
0
    break;
525
526
0
  case FR_PACKET_TYPE_VALUE_ADVERTISE:
527
0
  case FR_PACKET_TYPE_VALUE_REPLY:
528
0
  case FR_PACKET_TYPE_VALUE_RECONFIGURE:
529
0
  default:
530
0
    fr_strerror_const("Invalid message type sent to server");
531
0
    return false;
532
0
  }
533
0
  return true;
534
0
}
535
536
/** Verify the packet under some various circumstances
537
 *
538
 * @param[in] packet    to check.
539
 * @param[in] packet_len  The size of the packet data.
540
 * @param[in] packet_ctx  The expected packet_ctx
541
 * @param[in] from_server true for packets from a server, false for packets from a client.
542
 * @return
543
 *  - True on success.
544
 *  - False on failure.
545
 *
546
 *  fr_dhcpv6_ok() SHOULD be called before calling this function.
547
 */
548
bool fr_dhcpv6_verify(uint8_t const *packet, size_t packet_len, fr_dhcpv6_decode_ctx_t const *packet_ctx,
549
          bool from_server)
550
0
{
551
0
  if (packet_len < DHCPV6_HDR_LEN) return false;
552
553
  /*
554
   *  We support up to relaying.
555
   */
556
0
  if ((packet[0] == 0) || (packet[0] > FR_PACKET_TYPE_VALUE_RELAY_REPLY)) return false;
557
558
0
  if (!packet_ctx->duid) return false;
559
560
0
  if (from_server) return verify_to_client(packet, packet_len, packet_ctx);
561
562
0
  return verify_from_client(packet, packet_len, packet_ctx);
563
0
}
564
565
/*
566
567
       0                   1                   2                   3
568
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
569
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
570
      |    msg-type   |               transaction-id                  |
571
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
572
      |                                                               |
573
      .                            options                            .
574
      .                 (variable number and length)                  .
575
      |                                                               |
576
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
577
*/
578
579
/** Decode a DHCPv6 packet
580
 *
581
 */
582
ssize_t fr_dhcpv6_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *packet, size_t packet_len)
583
1.71k
{
584
1.71k
  ssize_t     slen = -1;
585
1.71k
  uint8_t const   *p, *end;
586
1.71k
  fr_dhcpv6_decode_ctx_t  packet_ctx = {};
587
1.71k
  fr_pair_t   *vp;
588
1.71k
  fr_pair_list_t    tmp;
589
590
1.71k
  if (packet_len < DHCPV6_HDR_LEN) return 0; /* protect access to packet[0] */
591
592
  /*
593
   *  Get the packet type.
594
   */
595
1.57k
  vp = fr_pair_afrom_da(ctx, attr_packet_type);
596
1.57k
  if (!vp) return -1;
597
598
1.57k
  fr_pair_list_init(&tmp);
599
1.57k
  vp->vp_uint32 = packet[0];
600
1.57k
  fr_pair_append(&tmp, vp);
601
602
1.57k
  switch (packet[0]) {
603
587
  case FR_DHCPV6_RELAY_FORWARD:
604
817
  case FR_DHCPV6_RELAY_REPLY:
605
    /*
606
     *  Just for sanity check.
607
     */
608
817
    if (packet_len < DHCPV6_RELAY_HDR_LEN) return -1;
609
610
    /*
611
     *  Decode the header fields.
612
     */
613
352
    vp = fr_pair_afrom_da(ctx, attr_hop_count);
614
352
    if (!vp) goto fail;
615
352
    if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, NULL,
616
352
                &FR_DBUFF_TMP(packet + 1, 1), 1, true) < 0) {
617
0
      goto fail;
618
0
    }
619
352
    fr_pair_append(&tmp, vp);
620
621
352
    vp = fr_pair_afrom_da(ctx, attr_relay_link_address);
622
352
    if (!vp) goto fail;
623
352
    if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, NULL,
624
352
                &FR_DBUFF_TMP(packet + 2, 16), 16, true) < 0) {
625
0
      goto fail;
626
0
    }
627
352
    fr_pair_append(&tmp, vp);
628
629
352
    vp = fr_pair_afrom_da(ctx, attr_relay_peer_address);
630
352
    if (!vp) goto fail;
631
352
    if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, NULL,
632
352
                &FR_DBUFF_TMP(packet + 2 + 16, 16), 16, true) < 0) {
633
0
      goto fail;
634
0
    }
635
636
352
    fr_pair_append(&tmp, vp);
637
638
352
    p = packet + DHCPV6_RELAY_HDR_LEN;
639
352
    goto decode_options;
640
641
757
  default:
642
757
    break;
643
1.57k
  }
644
645
  /*
646
   *  And the transaction ID.
647
   */
648
757
  vp = fr_pair_afrom_da(ctx, attr_transaction_id);
649
757
  if (!vp) {
650
631
  fail:
651
631
    fr_pair_list_free(&tmp);
652
631
    return slen;
653
0
  }
654
655
  /*
656
   *  Copy 3 octets over.
657
   */
658
757
  (void) fr_pair_value_memdup(vp, packet + 1, 3, false);
659
660
757
  fr_pair_append(&tmp, vp);
661
662
757
  p = packet + 4;
663
664
1.10k
decode_options:
665
1.10k
  end = packet + packet_len;
666
1.10k
  packet_ctx.tmp_ctx = talloc_init_const("tmp");
667
668
  /*
669
   *  The caller MUST have called fr_dhcpv6_ok() first.  If
670
   *  he doesn't, all hell breaks loose.
671
   */
672
2.75k
  while (p < end) {
673
2.27k
    slen = fr_dhcpv6_decode_option(ctx, &tmp, p, (end - p), &packet_ctx);
674
2.27k
    if (slen < 0) {
675
631
      talloc_free(packet_ctx.tmp_ctx);
676
631
      goto fail;
677
631
    }
678
    /*
679
     *  If slen is larger than the room in the packet,
680
     *  all kinds of bad things happen.
681
     */
682
1.64k
     if (!fr_cond_assert(slen <= (end - p))) {
683
0
      talloc_free(packet_ctx.tmp_ctx);
684
0
      goto fail;
685
0
    }
686
687
1.64k
     p += slen;
688
1.64k
     talloc_free_children(packet_ctx.tmp_ctx);
689
1.64k
  }
690
478
  fr_pair_list_append(out, &tmp);
691
692
  /*
693
   *  We've parsed the whole packet, return that.
694
   */
695
478
  talloc_free(packet_ctx.tmp_ctx);
696
478
  return packet_len;
697
1.10k
}
698
699
/** DHCPV6-specific iterator
700
 *
701
 */
702
void *fr_dhcpv6_next_encodable(fr_dcursor_t *cursor, void *current, void *uctx)
703
0
{
704
0
  fr_pair_t *c = current;
705
0
  fr_dict_t *dict = talloc_get_type_abort(uctx, fr_dict_t);
706
707
0
  while ((c = fr_dlist_next(cursor->dlist, c))) {
708
0
    PAIR_VERIFY(c);
709
0
    if (c->da->dict != dict || c->da->flags.internal) continue;
710
0
    if (c->vp_type == FR_TYPE_BOOL && !c->vp_bool) continue;
711
712
0
    break;
713
0
  }
714
715
0
  return c;
716
0
}
717
718
/** Encode a DHCPv6 packet
719
 *
720
 */
721
ssize_t fr_dhcpv6_encode(fr_dbuff_t *dbuff, uint8_t const *original, size_t length, int msg_type, fr_pair_list_t *vps)
722
0
{
723
0
  fr_dbuff_t    frame_dbuff = FR_DBUFF(dbuff);
724
0
  fr_pair_t   *vp;
725
0
  fr_dict_attr_t const  *root;
726
0
  ssize_t     slen;
727
0
  fr_dcursor_t    cursor;
728
0
  fr_dhcpv6_encode_ctx_t  packet_ctx;
729
730
0
  root = fr_dict_root(dict_dhcpv6);
731
732
0
  if (!msg_type) {
733
0
    vp = fr_pair_find_by_da(vps, NULL, attr_packet_type);
734
0
    if (vp) msg_type = vp->vp_uint32;
735
0
  }
736
737
0
  if ((msg_type <= 0) || (msg_type > UINT8_MAX)) {
738
0
    fr_strerror_printf("Invalid message type %d", msg_type);
739
0
    return -1;
740
0
  }
741
742
0
  FR_DBUFF_IN_RETURN(&frame_dbuff, (uint8_t)msg_type);
743
744
0
  switch (msg_type) {
745
0
  case FR_DHCPV6_RELAY_REPLY:
746
0
  case FR_DHCPV6_RELAY_FORWARD:
747
0
    vp = fr_pair_find_by_da(vps, NULL, attr_hop_count);
748
0
    if (likely(vp != NULL)) {
749
0
      FR_VALUE_BOX_TO_NETWORK_RETURN(&frame_dbuff, &vp->data);
750
0
    } else {
751
0
      FR_DBUFF_MEMSET_RETURN(&frame_dbuff, 0, DHCPV6_HOP_COUNT_LEN);
752
0
    }
753
754
0
    vp = fr_pair_find_by_da(vps, NULL, attr_relay_link_address);
755
0
    if (likely(vp != NULL)) {
756
0
      FR_VALUE_BOX_TO_NETWORK_RETURN(&frame_dbuff, &vp->data);
757
0
    } else {
758
0
      FR_DBUFF_MEMSET_RETURN(&frame_dbuff, 0, DHCPV6_LINK_ADDRESS_LEN);
759
0
    }
760
761
0
    vp = fr_pair_find_by_da(vps, NULL, attr_relay_peer_address);
762
0
    if (likely(vp != NULL)) {
763
0
      FR_VALUE_BOX_TO_NETWORK_RETURN(&frame_dbuff, &vp->data);
764
0
    } else {
765
0
      FR_DBUFF_MEMSET_RETURN(&frame_dbuff, 0, DHCPV6_PEER_ADDRESS_LEN);
766
0
    }
767
0
    break;
768
769
0
  default:
770
    /*
771
     *  We can set an XID, or we can pick a random one.
772
     */
773
0
    vp = fr_pair_find_by_da(vps, NULL, attr_transaction_id);
774
0
    if (vp && (vp->vp_length >= DHCPV6_TRANSACTION_ID_LEN)) {
775
0
      FR_DBUFF_IN_MEMCPY_RETURN(&frame_dbuff, vp->vp_octets, DHCPV6_TRANSACTION_ID_LEN);
776
0
    } else {
777
0
      uint8_t id[DHCPV6_TRANSACTION_ID_LEN];
778
0
      fr_nbo_from_uint24(id, fr_rand());
779
0
      FR_DBUFF_IN_MEMCPY_RETURN(&frame_dbuff, id, sizeof(id)); /* Need 24 bits of the 32bit integer */
780
0
    }
781
0
    break;
782
0
  }
783
784
  /*
785
   * Encode options.
786
   */
787
0
  packet_ctx.root = root;
788
0
  packet_ctx.original = original;
789
0
  packet_ctx.original_length = length;
790
791
0
  fr_pair_dcursor_iter_init(&cursor, vps, fr_dhcpv6_next_encodable, dict_dhcpv6);
792
0
  while ((fr_dbuff_extend(&frame_dbuff) > 0) && (fr_dcursor_current(&cursor) != NULL)) {
793
0
    slen = fr_dhcpv6_encode_option(&frame_dbuff, &cursor, &packet_ctx);
794
0
    if (slen < 0) return FR_DBUFF_ERROR_OFFSET(slen, fr_dbuff_used(&frame_dbuff));
795
0
  }
796
797
0
  return fr_dbuff_set(dbuff, &frame_dbuff);
798
0
}
799
800
801
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";
802
803
static void print_hex_data(FILE *fp, uint8_t const *ptr, int attrlen, int depth)
804
0
{
805
0
  int i;
806
807
0
  for (i = 0; i < attrlen; i++) {
808
0
    if ((i > 0) && ((i & 0x0f) == 0x00))
809
0
      fprintf(fp, "%.*s", depth, tabs);
810
0
    fprintf(fp, "%02x ", ptr[i]);
811
0
    if ((i & 0x0f) == 0x0f) fprintf(fp, "\n");
812
0
  }
813
0
  if ((i & 0x0f) != 0) fprintf(fp, "\n");
814
0
}
815
816
static void dhcpv6_print_hex(FILE *fp, uint8_t const *packet, size_t packet_len, int depth)
817
0
{
818
0
  uint8_t const *option, *end = packet + packet_len;
819
820
0
  if (packet_len < 4) {
821
0
    fprintf(fp, "%.*s", depth, tabs);
822
0
    fprintf(fp, "???:\t");
823
0
    print_hex_data(fp, packet, packet_len, depth + 1);
824
0
    return;
825
0
  }
826
827
0
  fprintf(fp, "%.*s", depth, tabs);
828
0
  if ((packet[0] > 0) && (packet[0] < FR_DHCPV6_CODE_MAX)) {
829
0
    fprintf(fp, "packet: %s\n", fr_dhcpv6_packet_names[packet[0]]);
830
0
  } else {
831
0
    fprintf(fp, "packet: %02x\n", packet[0]);
832
0
  }
833
834
0
  if ((packet[0] == FR_PACKET_TYPE_VALUE_RELAY_FORWARD) ||
835
0
      (packet[0] == FR_PACKET_TYPE_VALUE_RELAY_REPLY)) {
836
0
    if (packet_len < 34) {
837
0
      fprintf(fp, "%.*s", depth, tabs);
838
0
      fprintf(fp, "???:\t");
839
0
      print_hex_data(fp, packet + 1, packet_len - 1, depth + 1);
840
0
      return;
841
0
    }
842
843
0
    fprintf(fp, "%.*s", depth, tabs);
844
0
    fprintf(fp, "hops: %02x\n", packet[1]);
845
0
    fprintf(fp, "%.*s", depth, tabs);
846
0
    fprintf(fp, "relay link address: ");
847
0
    print_hex_data(fp, packet + 2, 16, depth + 1);
848
849
0
    fprintf(fp, "%.*s", depth, tabs);
850
0
    fprintf(fp, "peer address:       ");
851
0
    print_hex_data(fp, packet + 18, 16, depth + 1);
852
0
    option = packet + 34;
853
0
  } else {
854
0
    fprintf(fp, "%.*s", depth, tabs);
855
0
    fprintf(fp, "transaction id: ");
856
0
    print_hex_data(fp, packet + 1, 3, depth + 1);
857
0
    option = packet + 4;
858
0
  }
859
860
0
  fprintf(fp, "%.*s", depth, tabs);
861
0
  fprintf(fp, "options\n");
862
0
  while (option < end) {
863
0
    uint16_t length;
864
865
0
    if ((end - option) < 4) {
866
0
      fprintf(fp, "%.*s", depth + 1, tabs);
867
0
      fprintf(fp, "???:\t");
868
0
      print_hex_data(fp, option, end - option, depth + 2);
869
0
      break;
870
0
    }
871
872
0
    length = fr_nbo_to_uint16(option + 2);
873
0
    fprintf(fp, "%.*s", depth + 1, tabs);
874
0
    fprintf(fp, "%04x %04x\t", fr_nbo_to_uint16(option), length);
875
876
0
    if (length > end - (option + 4)) {
877
0
      print_hex_data(fp, option + 4, end - (option + 4), depth + 3);
878
0
      break;
879
0
    }
880
881
0
    print_hex_data(fp, option + 4, length, depth + 3);
882
0
    if ((option[0] == 0) && (option[1] == attr_relay_message->attr)) {
883
0
      dhcpv6_print_hex(fp, option + 4, length, depth + 2);
884
0
    }
885
886
0
    option += 4 + length;
887
0
  }
888
889
0
  fprintf(fp, "\n");
890
0
}
891
892
/** Print a raw DHCP packet as hex.
893
 *
894
 */
895
void fr_dhcpv6_print_hex(FILE *fp, uint8_t const *packet, size_t packet_len)
896
0
{
897
0
  dhcpv6_print_hex(fp, packet, packet_len, 0);
898
0
}
899
900
int fr_dhcpv6_global_init(void)
901
2
{
902
2
  if (instance_count > 0) {
903
0
    instance_count++;
904
0
    return 0;
905
0
  }
906
907
2
  instance_count++;
908
909
2
  if (fr_dict_autoload(libfreeradius_dhcpv6_dict) < 0) {
910
0
  fail:
911
0
    instance_count--;
912
0
    return -1;
913
0
  }
914
915
2
  if (fr_dict_attr_autoload(libfreeradius_dhcpv6_dict_attr) < 0) {
916
0
    fr_dict_autofree(libfreeradius_dhcpv6_dict);
917
0
    goto fail;
918
0
  }
919
920
2
  instantiated = true;
921
2
  return 0;
922
2
}
923
924
void fr_dhcpv6_global_free(void)
925
0
{
926
0
  if (!instantiated) return;
927
928
0
  fr_assert(instance_count > 0);
929
930
0
  if (--instance_count > 0) return;
931
932
0
  fr_dict_autofree(libfreeradius_dhcpv6_dict);
933
0
  instantiated = false;
934
0
}
935
936
static bool attr_valid(fr_dict_attr_t *da)
937
762
{
938
  /*
939
   *  DNS labels are strings, but are known width.
940
   */
941
762
  if (fr_dhcpv6_flag_any_dns_label(da)) {
942
40
    if (da->type != FR_TYPE_STRING) {
943
0
      fr_strerror_const("The 'dns_label' flag can only be used with attributes of type 'string'");
944
0
      return false;
945
0
    }
946
947
40
    da->flags.is_known_width = true;
948
40
    da->flags.length = 0;
949
40
  }
950
951
762
  if (da->type == FR_TYPE_ATTR)  {
952
2
    da->flags.is_known_width = true;
953
2
    da->flags.length = 2;
954
2
  }
955
956
762
  if (da_is_length_field8(da)) {
957
0
    fr_strerror_const("The 'length=uint8' flag cannot be used for DHCPv6");
958
0
    return false;
959
0
  }
960
961
  /*
962
   *  "arrays" of string/octets are encoded as a 16-bit
963
   *  length, followed by the actual data.
964
   */
965
762
  if (da->flags.array) {
966
56
    if ((da->type == FR_TYPE_STRING) || (da->type == FR_TYPE_OCTETS)) {
967
22
      if (da->flags.extra && !da_is_length_field16(da)) {
968
0
        fr_strerror_const("Invalid flags");
969
0
        return false;
970
0
      }
971
972
22
      da->flags.is_known_width = true;
973
22
      da->flags.extra = true;
974
22
      da->flags.subtype = FLAG_LENGTH_UINT16;
975
22
    }
976
977
56
    if (!da->flags.is_known_width) {
978
0
      fr_strerror_const("DHCPv6 arrays require data types which have known width");
979
0
      return false;
980
0
    }
981
56
  }
982
983
  /*
984
   *  "extra" signifies that subtype is being used by the
985
   *  dictionaries itself.
986
   */
987
762
  if (da->flags.extra || !da->flags.subtype) return true;
988
989
0
  if ((da->type == FR_TYPE_ATTR) && !da->parent->flags.is_root) {
990
0
    fr_strerror_const("The 'attribute' data type can only be used at the dictionary root");
991
0
    return false;
992
0
  }
993
994
0
  da->flags.is_known_width = true;
995
996
0
  return true;
997
0
}
998
999
extern fr_dict_protocol_t libfreeradius_dhcpv6_dict_protocol;
1000
fr_dict_protocol_t libfreeradius_dhcpv6_dict_protocol = {
1001
  .name = "dhcpv6",
1002
  .default_type_size = 2,
1003
  .default_type_length = 2,
1004
1005
  .attr = {
1006
    .valid = attr_valid,
1007
    .flags = {
1008
      .table = dhcpv6_flags,
1009
      .table_len = NUM_ELEMENTS(dhcpv6_flags),
1010
      .len = sizeof(fr_dhcpv6_attr_flags_t)
1011
    }
1012
  },
1013
1014
  .init = fr_dhcpv6_global_init,
1015
  .free = fr_dhcpv6_global_free,
1016
1017
  .encode   = fr_dhcpv6_encode_foreign,
1018
  .decode   = fr_dhcpv6_decode_foreign,
1019
};