Coverage Report

Created: 2026-04-12 06:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/radvd/process.c
Line
Count
Source
1
/*
2
 *
3
 *   Authors:
4
 *    Pedro Roque   <roque@di.fc.ul.pt>
5
 *    Lars Fenneberg    <lf@elemental.net>
6
 *
7
 *   This software is Copyright 1996,1997 by the above mentioned author(s),
8
 *   All Rights Reserved.
9
 *
10
 *   The license which is distributed with this software in the file COPYRIGHT
11
 *   applies to this software. If your distribution is missing this file, you
12
 *   may request it from <reubenhwk@gmail.com>.
13
 *
14
 */
15
16
#include "config.h"
17
#include "includes.h"
18
#include "radvd.h"
19
20
static void process_rs(int sock, struct Interface *, unsigned char *msg, int len, struct sockaddr_in6 *);
21
static void process_ra(struct Interface *, unsigned char *msg, int len, struct sockaddr_in6 *);
22
static int addr_match(struct in6_addr *a1, struct in6_addr *a2, int prefixlen);
23
24
void process(int sock, struct Interface *interfaces, unsigned char *msg, int len, struct sockaddr_in6 *addr,
25
       struct in6_pktinfo *pkt_info, int hoplimit)
26
613
{
27
613
  char if_namebuf[IF_NAMESIZE] = {""};
28
613
  char *if_name = if_indextoname(pkt_info->ipi6_ifindex, if_namebuf);
29
613
  if (!if_name) {
30
0
    if_name = "unknown interface";
31
0
  }
32
613
  dlog(LOG_DEBUG, 4, "%s received a packet", if_name);
33
34
613
  char addr_str[INET6_ADDRSTRLEN];
35
613
  addrtostr(&addr->sin6_addr, addr_str, sizeof(addr_str));
36
37
613
  if (!pkt_info) {
38
0
    flog(LOG_WARNING, "%s received packet with no pkt_info from %s!", if_name, addr_str);
39
0
    return;
40
0
  }
41
42
  /*
43
   * can this happen?
44
   */
45
46
613
  if (len < sizeof(struct icmp6_hdr)) {
47
0
    flog(LOG_WARNING, "%s received icmpv6 packet with invalid length (%d) from %s", if_name, len, addr_str);
48
0
    return;
49
0
  }
50
51
613
  struct icmp6_hdr *icmph = (struct icmp6_hdr *)msg;
52
53
613
  if (icmph->icmp6_type != ND_ROUTER_SOLICIT && icmph->icmp6_type != ND_ROUTER_ADVERT) {
54
    /*
55
     *      We just want to listen to RSs and RAs
56
     */
57
58
15
    flog(LOG_ERR, "%s icmpv6 filter failed", if_name);
59
15
    return;
60
15
  }
61
62
598
  if (icmph->icmp6_type == ND_ROUTER_ADVERT) {
63
501
    if (len < sizeof(struct nd_router_advert)) {
64
5
      flog(LOG_WARNING, "%s received icmpv6 RA packet with invalid length (%d) from %s", if_name, len,
65
5
           addr_str);
66
5
      return;
67
5
    }
68
69
496
    if (!IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr)) {
70
0
      flog(LOG_WARNING, "%s received icmpv6 RA packet with non-linklocal source address from %s", if_name,
71
0
           addr_str);
72
0
      return;
73
0
    }
74
496
  }
75
76
593
  if (icmph->icmp6_type == ND_ROUTER_SOLICIT) {
77
97
    if (len < sizeof(struct nd_router_solicit)) {
78
0
      flog(LOG_WARNING, "%s received icmpv6 RS packet with invalid length (%d) from %s", if_name, len,
79
0
           addr_str);
80
0
      return;
81
0
    }
82
97
  }
83
84
593
  if (icmph->icmp6_code != 0) {
85
9
    flog(LOG_WARNING, "%s received icmpv6 RS/RA packet with invalid code (%d) from %s", if_name, icmph->icmp6_code,
86
9
         addr_str);
87
9
    return;
88
9
  }
89
90
  /* get iface by received if_index */
91
584
  struct Interface *iface = find_iface_by_index(interfaces, pkt_info->ipi6_ifindex);
92
93
584
  if (iface == NULL) {
94
0
    dlog(LOG_WARNING, 4, "%s received icmpv6 RS/RA packet on an unknown interface with index %d", if_name,
95
0
         pkt_info->ipi6_ifindex);
96
0
    return;
97
0
  }
98
99
584
  if (!iface->state_info.ready && (0 != setup_iface(sock, iface))) {
100
0
    flog(LOG_WARNING, "%s received RS or RA on %s but %s is not ready and setup_iface failed", if_name,
101
0
         iface->props.name, iface->props.name);
102
0
    return;
103
0
  }
104
105
584
  if (hoplimit != 255) {
106
0
    flog(LOG_WARNING, "%s received RS or RA with invalid hoplimit %d from %s", if_name, hoplimit, addr_str);
107
0
    return;
108
0
  }
109
110
584
  if (icmph->icmp6_type == ND_ROUTER_SOLICIT) {
111
88
    dlog(LOG_DEBUG, 3, "%s received RS from: %s", if_name, addr_str);
112
88
    process_rs(sock, iface, msg, len, addr);
113
496
  } else if (icmph->icmp6_type == ND_ROUTER_ADVERT) {
114
496
    if (0 == memcmp(&addr->sin6_addr, &iface->props.if_addr, sizeof(iface->props.if_addr))) {
115
496
      dlog(LOG_DEBUG, 3, "%s received RA from: %s (myself)", if_name, addr_str);
116
496
    } else {
117
0
      dlog(LOG_DEBUG, 3, "%s received RA from: %s", if_name, addr_str);
118
0
    }
119
496
    process_ra(iface, msg, len, addr);
120
496
  }
121
584
}
122
123
static void process_rs(int sock, struct Interface *iface, unsigned char *msg, int len, struct sockaddr_in6 *addr)
124
88
{
125
  /* RFC 7772, section 5.1:
126
   * 5.1.  Network-Side Recommendations
127
   *    1.  Router manufacturers SHOULD allow network administrators to
128
   *        configure the routers to respond to Router Solicitations with
129
   *        unicast Router Advertisements if:
130
   *        *  The Router Solicitation's source address is not the
131
   *           unspecified address, and:
132
   *        *  The solicitation contains a valid Source Link-Layer Address
133
   *           option.
134
   *
135
   * However, testing shows that many clients do not set the SLLA option:
136
   * https://github.com/reubenhwk/radvd/issues/63#issuecomment-287172252
137
   * As of 2017/03/16:
138
   * - macOS 10.12.3 sierra - sends SLLA 2 times out of 4
139
   * - iOS 10.2.1 (iPhone 5s) - no SLLA
140
   * - Android 7.0 (sony xperia phone) - sends SLLA
141
   * - Android 5.1 (nexus 7 tablet) - sends SLLA
142
   * - Ubuntu 16.04.2 LTS w/ Network Manager, running 4.9 kernel (dell laptop) - no SLLA
143
   * - Windows 10 (dell laptop) - no SLLA
144
   *
145
   * We decide to ignore the SLLA option for now, and only require the
146
   * unspecified address check. Clients that did not set the SLLA option will
147
   * trigger a neighbour solicit to the solicited-node address trying to
148
   * resolve the link-local address to, this would still be less traffic than
149
   * the all-nodes multicast.
150
   */
151
88
  int rfc7772_unicast_response = iface->AdvRASolicitedUnicast && !IN6_IS_ADDR_UNSPECIFIED(&addr->sin6_addr);
152
153
  /* validation */
154
88
  len -= sizeof(struct nd_router_solicit);
155
156
88
  uint8_t *opt_str = (uint8_t *)(msg + sizeof(struct nd_router_solicit));
157
158
10.2k
  while (len > 0) {
159
10.2k
    if (len < 2) {
160
2
      flog(LOG_WARNING, "trailing garbage in RS");
161
2
      return;
162
2
    }
163
164
10.2k
    int const optlen = (opt_str[1] << 3);
165
166
10.2k
    if (optlen == 0) {
167
23
      flog(LOG_WARNING, "zero length option in RS");
168
23
      return;
169
10.2k
    } else if (optlen > len) {
170
39
      flog(LOG_WARNING, "option length greater than total length in RS");
171
39
      return;
172
39
    }
173
174
10.2k
    if (*opt_str == ND_OPT_SOURCE_LINKADDR && IN6_IS_ADDR_UNSPECIFIED(&addr->sin6_addr)) {
175
0
      flog(LOG_WARNING,
176
0
           "received icmpv6 RS packet with unspecified source address and there is a lladdr option");
177
0
      return;
178
0
    }
179
180
10.2k
    len -= optlen;
181
10.2k
    opt_str += optlen;
182
10.2k
  }
183
184
24
  struct timespec ts;
185
24
  clock_gettime(CLOCK_MONOTONIC, &ts);
186
187
24
  double const delay = (MAX_RA_DELAY_SECONDS * rand() / (RAND_MAX + 1.0));
188
189
24
  if (iface->UnicastOnly || rfc7772_unicast_response) {
190
0
    send_ra_forall(sock, iface, &addr->sin6_addr);
191
24
  } else if (timespecdiff(&ts, &iface->times.last_multicast) / 1000.0 < iface->MinDelayBetweenRAs) {
192
    /* last RA was sent only a few moments ago, don't send another immediately. */
193
0
    double next = iface->MinDelayBetweenRAs - (ts.tv_sec + ts.tv_nsec / 1000000000.0) +
194
0
            (iface->times.last_multicast.tv_sec + iface->times.last_multicast.tv_nsec / 1000000000.0) + delay;
195
0
    dlog(LOG_DEBUG, 5, "%s: rate limiting RA's, rescheduling RA %f seconds from now", iface->props.name, next);
196
0
    reschedule_iface(iface, next);
197
24
  } else {
198
    /* no RA sent in a while, send a multicast reply */
199
24
    send_ra_forall(sock, iface, NULL);
200
24
    double next = rand_between(iface->MinRtrAdvInterval, iface->MaxRtrAdvInterval);
201
24
    reschedule_iface(iface, next);
202
24
  }
203
24
  dlog(LOG_DEBUG, 2, "%s processed an RS", iface->props.name);
204
24
}
205
206
/*
207
 * check router advertisements according to RFC 4861, 6.2.7
208
 */
209
static void process_ra(struct Interface *iface, unsigned char *msg, int len, struct sockaddr_in6 *addr)
210
496
{
211
496
  char addr_str[INET6_ADDRSTRLEN];
212
496
  addrtostr(&addr->sin6_addr, addr_str, sizeof(addr_str));
213
214
496
  struct nd_router_advert *radvert = (struct nd_router_advert *)msg;
215
216
496
  if ((radvert->nd_ra_curhoplimit && iface->ra_header_info.AdvCurHopLimit) &&
217
0
      (radvert->nd_ra_curhoplimit != iface->ra_header_info.AdvCurHopLimit)) {
218
0
    flog(LOG_WARNING, "our AdvCurHopLimit on %s doesn't agree with %s", iface->props.name, addr_str);
219
0
  }
220
221
496
  if ((radvert->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) && !iface->ra_header_info.AdvManagedFlag) {
222
130
    flog(LOG_WARNING, "our AdvManagedFlag on %s doesn't agree with %s", iface->props.name, addr_str);
223
130
  }
224
225
496
  if ((radvert->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) && !iface->ra_header_info.AdvOtherConfigFlag) {
226
141
    flog(LOG_WARNING, "our AdvOtherConfigFlag on %s doesn't agree with %s", iface->props.name, addr_str);
227
141
  }
228
229
  /* note: we don't check the default router preference here, because they're likely different */
230
231
496
  if ((radvert->nd_ra_reachable && iface->ra_header_info.AdvReachableTime) &&
232
0
      (ntohl(radvert->nd_ra_reachable) != iface->ra_header_info.AdvReachableTime)) {
233
0
    flog(LOG_WARNING, "our AdvReachableTime on %s doesn't agree with %s", iface->props.name, addr_str);
234
0
  }
235
236
496
  if ((radvert->nd_ra_retransmit && iface->ra_header_info.AdvRetransTimer) &&
237
0
      (ntohl(radvert->nd_ra_retransmit) != iface->ra_header_info.AdvRetransTimer)) {
238
0
    flog(LOG_WARNING, "our AdvRetransTimer on %s doesn't agree with %s", iface->props.name, addr_str);
239
0
  }
240
241
496
  len -= sizeof(struct nd_router_advert);
242
243
496
  if (len == 0)
244
79
    return;
245
246
417
  uint8_t *opt_str = (uint8_t *)(msg + sizeof(struct nd_router_advert));
247
248
42.3k
  while (len > 0) {
249
250
42.1k
    if (len < 2) {
251
16
      flog(LOG_ERR, "trailing garbage in RA on %s from %s", iface->props.name, addr_str);
252
16
      break;
253
16
    }
254
255
42.1k
    int optlen = (opt_str[1] << 3);
256
257
42.1k
    if (optlen == 0) {
258
113
      flog(LOG_ERR, "zero length option in RA on %s from %s", iface->props.name, addr_str);
259
113
      break;
260
42.0k
    } else if (optlen > len) {
261
87
      flog(LOG_ERR, "option length (%d) greater than total"
262
87
              " length (%d) in RA on %s from %s",
263
87
           optlen, len, iface->props.name, addr_str);
264
87
      break;
265
87
    }
266
267
41.9k
    switch (*opt_str) {
268
2.49k
    case ND_OPT_MTU: {
269
2.49k
      struct nd_opt_mtu *mtu = (struct nd_opt_mtu *)opt_str;
270
2.49k
      if (len < sizeof(*mtu))
271
0
        return;
272
273
2.49k
      if (iface->AdvLinkMTU && (ntohl(mtu->nd_opt_mtu_mtu) != iface->AdvLinkMTU)) {
274
0
        flog(LOG_WARNING, "our AdvLinkMTU on %s doesn't agree with %s", iface->props.name, addr_str);
275
0
      }
276
2.49k
      break;
277
2.49k
    }
278
1.57k
    case ND_OPT_PREFIX_INFORMATION: {
279
1.57k
      struct nd_opt_prefix_info *pinfo = (struct nd_opt_prefix_info *)opt_str;
280
1.57k
      if (len < sizeof(*pinfo))
281
12
        return;
282
1.56k
      int preferred = ntohl(pinfo->nd_opt_pi_preferred_time);
283
1.56k
      int valid = ntohl(pinfo->nd_opt_pi_valid_time);
284
285
1.56k
      struct AdvPrefix *prefix = iface->AdvPrefixList;
286
1.56k
      while (prefix) {
287
0
        char prefix_str[INET6_ADDRSTRLEN];
288
0
        if ((prefix->PrefixLen == pinfo->nd_opt_pi_prefix_len) &&
289
0
            addr_match(&prefix->Prefix, &pinfo->nd_opt_pi_prefix, prefix->PrefixLen)) {
290
0
          addrtostr(&prefix->Prefix, prefix_str, sizeof(prefix_str));
291
292
0
          if (!prefix->DecrementLifetimesFlag && valid != prefix->AdvValidLifetime) {
293
0
            flog(LOG_WARNING, "our AdvValidLifetime on"
294
0
                  " %s for %s doesn't agree with %s",
295
0
                 iface->props.name, prefix_str, addr_str);
296
0
          }
297
0
          if (!prefix->DecrementLifetimesFlag && preferred != prefix->AdvPreferredLifetime) {
298
0
            flog(LOG_WARNING, "our AdvPreferredLifetime on"
299
0
                  " %s for %s doesn't agree with %s",
300
0
                 iface->props.name, prefix_str, addr_str);
301
0
          }
302
0
        }
303
304
0
        prefix = prefix->next;
305
0
      }
306
1.56k
      break;
307
1.57k
    }
308
200
    case ND_OPT_ROUTE_INFORMATION:
309
      /* not checked: these will very likely vary a lot */
310
200
      break;
311
15.1k
    case ND_OPT_SOURCE_LINKADDR:
312
      /* not checked */
313
15.1k
      break;
314
18.1k
    case ND_OPT_TARGET_LINKADDR:
315
18.4k
    case ND_OPT_REDIRECTED_HEADER:
316
18.4k
      flog(LOG_ERR, "invalid option %d in RA on %s from %s", (int)*opt_str, iface->props.name, addr_str);
317
18.4k
      break;
318
    /* Mobile IPv6 extensions */
319
224
    case ND_OPT_RTR_ADV_INTERVAL:
320
447
    case ND_OPT_HOME_AGENT_INFO:
321
      /* not checked */
322
447
      break;
323
1.04k
    case ND_OPT_RDNSS_INFORMATION: {
324
1.04k
      char rdnss_str[INET6_ADDRSTRLEN];
325
1.04k
      struct nd_opt_rdnss_info_local *rdnssinfo = (struct nd_opt_rdnss_info_local *)opt_str;
326
1.04k
      if (len < sizeof(*rdnssinfo))
327
0
        return;
328
329
      // must be a multiple of 2, in the range of 3..255
330
      // https://datatracker.ietf.org/doc/html/rfc8106#section-5.1
331
1.04k
      if (rdnssinfo->nd_opt_rdnssi_len >= 3 && rdnssinfo->nd_opt_rdnssi_len % 2 == 1) {
332
3.68k
        for (int i = 0; i < (rdnssinfo->nd_opt_rdnssi_len - 1) / 2; i++) {
333
3.30k
          if (!check_rdnss_presence(iface->AdvRDNSSList, &rdnssinfo->nd_opt_rdnssi_addr[i])) {
334
3.30k
            addrtostr(&rdnssinfo->nd_opt_rdnssi_addr[i], rdnss_str, sizeof(rdnss_str));
335
3.30k
            flog(LOG_WARNING, "RDNSS address %s received on %s from %s is not advertised by us",
336
3.30k
                 rdnss_str, iface->props.name, addr_str);
337
3.30k
          }
338
3.30k
        }
339
656
      } else {
340
656
        flog(LOG_ERR, "invalid len %i in RDNSS option on %s from %s",
341
656
             rdnssinfo->nd_opt_rdnssi_len, iface->props.name, addr_str);
342
656
      }
343
344
1.04k
      break;
345
1.04k
    }
346
1.09k
    case ND_OPT_DNSSL_INFORMATION: {
347
1.09k
      struct nd_opt_dnssl_info_local *dnsslinfo = (struct nd_opt_dnssl_info_local *)opt_str;
348
1.09k
      if (len < sizeof(*dnsslinfo))
349
0
        return;
350
351
16.3k
      for (int offset = 0; offset < (dnsslinfo->nd_opt_dnssli_len - 1) * 8;) {
352
15.7k
        char suffix[256] = {""};
353
15.7k
        if (&dnsslinfo->nd_opt_dnssli_suffixes[offset] - opt_str >= len)
354
0
          return;
355
15.7k
        int label_len = dnsslinfo->nd_opt_dnssli_suffixes[offset++];
356
357
15.7k
        if (label_len == 0) {
358
          /*
359
           * Ignore empty suffixes. They're
360
           * probably just padding...
361
           */
362
1.93k
          if (suffix[0] == '\0')
363
1.93k
            continue;
364
365
0
          if (!check_dnssl_presence(iface->AdvDNSSLList, suffix)) {
366
0
            flog(LOG_WARNING,
367
0
                 "DNSSL suffix %s received on %s from %s is not advertised by us", suffix,
368
0
                 iface->props.name, addr_str);
369
0
          }
370
371
0
          suffix[0] = '\0';
372
0
          continue;
373
1.93k
        }
374
375
        /*
376
         * 1) must not overflow int: label + 2, offset + label_len
377
         * 2) last byte of dnssli_suffix must not overflow opt_str + len
378
         */
379
13.8k
        if ((sizeof(suffix) - strlen(suffix)) < (label_len + 2) || label_len >= (INT_MAX - 1) ||
380
13.3k
            &dnsslinfo->nd_opt_dnssli_suffixes[offset + label_len] - opt_str >= len ||
381
13.3k
            offset + label_len < offset) {
382
500
          flog(LOG_ERR, "oversized suffix in DNSSL option on %s from %s", iface->props.name,
383
500
               addr_str);
384
500
          break;
385
500
        }
386
387
13.3k
        if (suffix[0] != '\0')
388
0
          strcat(suffix, ".");
389
13.3k
        strncat(suffix, (char *)&dnsslinfo->nd_opt_dnssli_suffixes[offset], label_len);
390
13.3k
        offset += label_len;
391
13.3k
      }
392
1.09k
      break;
393
1.09k
    }
394
1.09k
    case ND_OPT_PREF64:
395
      /* not checked */
396
212
      break;
397
1.20k
    default:
398
1.20k
      dlog(LOG_DEBUG, 1, "unknown option %d in RA on %s from %s", (int)*opt_str, iface->props.name, addr_str);
399
1.20k
      break;
400
41.9k
    }
401
402
41.9k
    len -= optlen;
403
41.9k
    opt_str += optlen;
404
41.9k
  }
405
405
  dlog(LOG_DEBUG, 2, "processed RA on %s", iface->props.name);
406
405
}
407
408
static int addr_match(struct in6_addr *a1, struct in6_addr *a2, int prefixlen)
409
0
{
410
0
  unsigned int pdw = prefixlen >> 0x05; /* num of whole uint32_t in prefix */
411
0
  if (pdw) {
412
0
    if (memcmp(a1, a2, pdw << 2))
413
0
      return 0;
414
0
  }
415
416
0
  unsigned int pbi = prefixlen & 0x1f; /* num of bits in incomplete uint32_t in prefix */
417
0
  if (pbi) {
418
0
    uint32_t w1 = *((uint32_t *)a1 + pdw);
419
0
    uint32_t w2 = *((uint32_t *)a2 + pdw);
420
421
0
    uint32_t mask = htonl(((uint32_t)0xffffffff) << (0x20 - pbi));
422
423
0
    if ((w1 ^ w2) & mask)
424
0
      return 0;
425
0
  }
426
427
0
  return 1;
428
0
}