Coverage Report

Created: 2026-02-14 07:03

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
609
{
27
609
  char if_namebuf[IF_NAMESIZE] = {""};
28
609
  char *if_name = if_indextoname(pkt_info->ipi6_ifindex, if_namebuf);
29
609
  if (!if_name) {
30
0
    if_name = "unknown interface";
31
0
  }
32
609
  dlog(LOG_DEBUG, 4, "%s received a packet", if_name);
33
34
609
  char addr_str[INET6_ADDRSTRLEN];
35
609
  addrtostr(&addr->sin6_addr, addr_str, sizeof(addr_str));
36
37
609
  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
609
  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
609
  struct icmp6_hdr *icmph = (struct icmp6_hdr *)msg;
52
53
609
  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
17
    flog(LOG_ERR, "%s icmpv6 filter failed", if_name);
59
17
    return;
60
17
  }
61
62
592
  if (icmph->icmp6_type == ND_ROUTER_ADVERT) {
63
493
    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
488
    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
488
  }
75
76
587
  if (icmph->icmp6_type == ND_ROUTER_SOLICIT) {
77
99
    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
99
  }
83
84
587
  if (icmph->icmp6_code != 0) {
85
8
    flog(LOG_WARNING, "%s received icmpv6 RS/RA packet with invalid code (%d) from %s", if_name, icmph->icmp6_code,
86
8
         addr_str);
87
8
    return;
88
8
  }
89
90
  /* get iface by received if_index */
91
579
  struct Interface *iface = find_iface_by_index(interfaces, pkt_info->ipi6_ifindex);
92
93
579
  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
579
  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
579
  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
579
  if (icmph->icmp6_type == ND_ROUTER_SOLICIT) {
111
91
    dlog(LOG_DEBUG, 3, "%s received RS from: %s", if_name, addr_str);
112
91
    process_rs(sock, iface, msg, len, addr);
113
488
  } else if (icmph->icmp6_type == ND_ROUTER_ADVERT) {
114
488
    if (0 == memcmp(&addr->sin6_addr, &iface->props.if_addr, sizeof(iface->props.if_addr))) {
115
488
      dlog(LOG_DEBUG, 3, "%s received RA from: %s (myself)", if_name, addr_str);
116
488
    } else {
117
0
      dlog(LOG_DEBUG, 3, "%s received RA from: %s", if_name, addr_str);
118
0
    }
119
488
    process_ra(iface, msg, len, addr);
120
488
  }
121
579
}
122
123
static void process_rs(int sock, struct Interface *iface, unsigned char *msg, int len, struct sockaddr_in6 *addr)
124
91
{
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
91
  int rfc7772_unicast_response = iface->AdvRASolicitedUnicast && !IN6_IS_ADDR_UNSPECIFIED(&addr->sin6_addr);
152
153
  /* validation */
154
91
  len -= sizeof(struct nd_router_solicit);
155
156
91
  uint8_t *opt_str = (uint8_t *)(msg + sizeof(struct nd_router_solicit));
157
158
13.5k
  while (len > 0) {
159
13.5k
    if (len < 2) {
160
2
      flog(LOG_WARNING, "trailing garbage in RS");
161
2
      return;
162
2
    }
163
164
13.5k
    int const optlen = (opt_str[1] << 3);
165
166
13.5k
    if (optlen == 0) {
167
27
      flog(LOG_WARNING, "zero length option in RS");
168
27
      return;
169
13.5k
    } else if (optlen > len) {
170
37
      flog(LOG_WARNING, "option length greater than total length in RS");
171
37
      return;
172
37
    }
173
174
13.4k
    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
13.4k
    len -= optlen;
181
13.4k
    opt_str += optlen;
182
13.4k
  }
183
184
25
  struct timespec ts;
185
25
  clock_gettime(CLOCK_MONOTONIC, &ts);
186
187
25
  double const delay = (MAX_RA_DELAY_SECONDS * rand() / (RAND_MAX + 1.0));
188
189
25
  if (iface->UnicastOnly || rfc7772_unicast_response) {
190
0
    send_ra_forall(sock, iface, &addr->sin6_addr);
191
25
  } 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
25
  } else {
198
    /* no RA sent in a while, send a multicast reply */
199
25
    send_ra_forall(sock, iface, NULL);
200
25
    double next = rand_between(iface->MinRtrAdvInterval, iface->MaxRtrAdvInterval);
201
25
    reschedule_iface(iface, next);
202
25
  }
203
25
  dlog(LOG_DEBUG, 2, "%s processed an RS", iface->props.name);
204
25
}
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
488
{
211
488
  char addr_str[INET6_ADDRSTRLEN];
212
488
  addrtostr(&addr->sin6_addr, addr_str, sizeof(addr_str));
213
214
488
  struct nd_router_advert *radvert = (struct nd_router_advert *)msg;
215
216
488
  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
488
  if ((radvert->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) && !iface->ra_header_info.AdvManagedFlag) {
222
145
    flog(LOG_WARNING, "our AdvManagedFlag on %s doesn't agree with %s", iface->props.name, addr_str);
223
145
  }
224
225
488
  if ((radvert->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) && !iface->ra_header_info.AdvOtherConfigFlag) {
226
129
    flog(LOG_WARNING, "our AdvOtherConfigFlag on %s doesn't agree with %s", iface->props.name, addr_str);
227
129
  }
228
229
  /* note: we don't check the default router preference here, because they're likely different */
230
231
488
  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
488
  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
488
  len -= sizeof(struct nd_router_advert);
242
243
488
  if (len == 0)
244
74
    return;
245
246
414
  uint8_t *opt_str = (uint8_t *)(msg + sizeof(struct nd_router_advert));
247
248
56.0k
  while (len > 0) {
249
250
55.8k
    if (len < 2) {
251
15
      flog(LOG_ERR, "trailing garbage in RA on %s from %s", iface->props.name, addr_str);
252
15
      break;
253
15
    }
254
255
55.8k
    int optlen = (opt_str[1] << 3);
256
257
55.8k
    if (optlen == 0) {
258
111
      flog(LOG_ERR, "zero length option in RA on %s from %s", iface->props.name, addr_str);
259
111
      break;
260
55.7k
    } else if (optlen > len) {
261
92
      flog(LOG_ERR, "option length (%d) greater than total"
262
92
              " length (%d) in RA on %s from %s",
263
92
           optlen, len, iface->props.name, addr_str);
264
92
      break;
265
92
    }
266
267
55.6k
    switch (*opt_str) {
268
3.81k
    case ND_OPT_MTU: {
269
3.81k
      struct nd_opt_mtu *mtu = (struct nd_opt_mtu *)opt_str;
270
3.81k
      if (len < sizeof(*mtu))
271
0
        return;
272
273
3.81k
      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
3.81k
      break;
277
3.81k
    }
278
1.96k
    case ND_OPT_PREFIX_INFORMATION: {
279
1.96k
      struct nd_opt_prefix_info *pinfo = (struct nd_opt_prefix_info *)opt_str;
280
1.96k
      if (len < sizeof(*pinfo))
281
11
        return;
282
1.95k
      int preferred = ntohl(pinfo->nd_opt_pi_preferred_time);
283
1.95k
      int valid = ntohl(pinfo->nd_opt_pi_valid_time);
284
285
1.95k
      struct AdvPrefix *prefix = iface->AdvPrefixList;
286
1.95k
      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.95k
      break;
307
1.96k
    }
308
209
    case ND_OPT_ROUTE_INFORMATION:
309
      /* not checked: these will very likely vary a lot */
310
209
      break;
311
20.2k
    case ND_OPT_SOURCE_LINKADDR:
312
      /* not checked */
313
20.2k
      break;
314
24.5k
    case ND_OPT_TARGET_LINKADDR:
315
24.9k
    case ND_OPT_REDIRECTED_HEADER:
316
24.9k
      flog(LOG_ERR, "invalid option %d in RA on %s from %s", (int)*opt_str, iface->props.name, addr_str);
317
24.9k
      break;
318
    /* Mobile IPv6 extensions */
319
468
    case ND_OPT_RTR_ADV_INTERVAL:
320
712
    case ND_OPT_HOME_AGENT_INFO:
321
      /* not checked */
322
712
      break;
323
1.10k
    case ND_OPT_RDNSS_INFORMATION: {
324
1.10k
      char rdnss_str[INET6_ADDRSTRLEN];
325
1.10k
      struct nd_opt_rdnss_info_local *rdnssinfo = (struct nd_opt_rdnss_info_local *)opt_str;
326
1.10k
      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.10k
      if (rdnssinfo->nd_opt_rdnssi_len >= 3 && rdnssinfo->nd_opt_rdnssi_len % 2 == 1) {
332
4.48k
        for (int i = 0; i < (rdnssinfo->nd_opt_rdnssi_len - 1) / 2; i++) {
333
4.00k
          if (!check_rdnss_presence(iface->AdvRDNSSList, &rdnssinfo->nd_opt_rdnssi_addr[i])) {
334
4.00k
            addrtostr(&rdnssinfo->nd_opt_rdnssi_addr[i], rdnss_str, sizeof(rdnss_str));
335
4.00k
            flog(LOG_WARNING, "RDNSS address %s received on %s from %s is not advertised by us",
336
4.00k
                 rdnss_str, iface->props.name, addr_str);
337
4.00k
          }
338
4.00k
        }
339
626
      } else {
340
626
        flog(LOG_ERR, "invalid len %i in RDNSS option on %s from %s",
341
626
             rdnssinfo->nd_opt_rdnssi_len, iface->props.name, addr_str);
342
626
      }
343
344
1.10k
      break;
345
1.10k
    }
346
999
    case ND_OPT_DNSSL_INFORMATION: {
347
999
      struct nd_opt_dnssl_info_local *dnsslinfo = (struct nd_opt_dnssl_info_local *)opt_str;
348
999
      if (len < sizeof(*dnsslinfo))
349
0
        return;
350
351
17.3k
      for (int offset = 0; offset < (dnsslinfo->nd_opt_dnssli_len - 1) * 8;) {
352
16.6k
        char suffix[256] = {""};
353
16.6k
        if (&dnsslinfo->nd_opt_dnssli_suffixes[offset] - opt_str >= len)
354
0
          return;
355
16.6k
        int label_len = dnsslinfo->nd_opt_dnssli_suffixes[offset++];
356
357
16.6k
        if (label_len == 0) {
358
          /*
359
           * Ignore empty suffixes. They're
360
           * probably just padding...
361
           */
362
2.05k
          if (suffix[0] == '\0')
363
2.05k
            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
2.05k
        }
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
14.6k
        if ((sizeof(suffix) - strlen(suffix)) < (label_len + 2) || label_len >= (INT_MAX - 1) ||
380
14.3k
            &dnsslinfo->nd_opt_dnssli_suffixes[offset + label_len] - opt_str >= len ||
381
14.2k
            offset + label_len < offset) {
382
336
          flog(LOG_ERR, "oversized suffix in DNSSL option on %s from %s", iface->props.name,
383
336
               addr_str);
384
336
          break;
385
336
        }
386
387
14.2k
        if (suffix[0] != '\0')
388
0
          strcat(suffix, ".");
389
14.2k
        strncat(suffix, (char *)&dnsslinfo->nd_opt_dnssli_suffixes[offset], label_len);
390
14.2k
        offset += label_len;
391
14.2k
      }
392
999
      break;
393
999
    }
394
999
    case ND_OPT_PREF64:
395
      /* not checked */
396
244
      break;
397
1.50k
    default:
398
1.50k
      dlog(LOG_DEBUG, 1, "unknown option %d in RA on %s from %s", (int)*opt_str, iface->props.name, addr_str);
399
1.50k
      break;
400
55.6k
    }
401
402
55.6k
    len -= optlen;
403
55.6k
    opt_str += optlen;
404
55.6k
  }
405
403
  dlog(LOG_DEBUG, 2, "processed RA on %s", iface->props.name);
406
403
}
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
}