Coverage Report

Created: 2026-09-01 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dnsmasq/src/edns0.c
Line
Count
Source
1
/* dnsmasq is Copyright (c) 2000-2026 Simon Kelley
2
3
   This program is free software; you can redistribute it and/or modify
4
   it under the terms of the GNU General Public License as published by
5
   the Free Software Foundation; version 2 dated June, 1991, or
6
   (at your option) version 3 dated 29 June, 2007.
7
 
8
   This program is distributed in the hope that it will be useful,
9
   but WITHOUT ANY WARRANTY; without even the implied warranty of
10
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
   GNU General Public License for more details.
12
     
13
   You should have received a copy of the GNU General Public License
14
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
*/
16
17
#include "dnsmasq.h"
18
19
unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t  *len, unsigned char **p, int *is_sign, int *is_last)
20
0
{
21
  /* See if packet has an RFC2671 pseudoheader, and if so return a pointer to it. 
22
     also return length of pseudoheader in *len and pointer to the UDP size in *p
23
     Finally, check to see if a packet is signed. If it is we cannot change a single bit before
24
     forwarding. We look for TSIG in the addition section, and TKEY queries (for GSS-TSIG) */
25
  
26
0
  int i, arcount = ntohs(header->arcount);
27
0
  unsigned char *ansp = (unsigned char *)(header+1);
28
0
  unsigned short rdlen, type, class;
29
0
  unsigned char *ret = NULL;
30
31
0
  if (is_sign)
32
0
    {
33
0
      *is_sign = 0;
34
35
0
      if (OPCODE(header) == QUERY)
36
0
  {
37
0
    for (i = ntohs(header->qdcount); i != 0; i--)
38
0
      {
39
0
        if (!(ansp = skip_name(ansp, header, plen, 4)))
40
0
    return NULL;
41
        
42
0
        GETSHORT(type, ansp); 
43
0
        GETSHORT(class, ansp);
44
        
45
0
        if (class == C_IN && type == T_TKEY)
46
0
    *is_sign = 1;
47
0
      }
48
0
  }
49
0
    }
50
0
  else
51
0
    {
52
0
      if (!(ansp = skip_questions(header, plen)))
53
0
  return NULL;
54
0
    }
55
    
56
0
  if (arcount == 0)
57
0
    return NULL;
58
  
59
0
  if (!(ansp = skip_section(ansp, ntohs(header->ancount) + ntohs(header->nscount), header, plen)))
60
0
    return NULL; 
61
  
62
0
  for (i = 0; i < arcount; i++)
63
0
    {
64
0
      unsigned char *save, *start = ansp;
65
0
      if (!(ansp = skip_name(ansp, header, plen, 10)))
66
0
  return NULL; 
67
68
0
      GETSHORT(type, ansp);
69
0
      save = ansp;
70
0
      GETSHORT(class, ansp);
71
0
      ansp += 4; /* TTL */
72
0
      GETSHORT(rdlen, ansp);
73
0
      if (!ADD_RDLEN(header, ansp, plen, rdlen))
74
0
  return NULL;
75
0
      if (type == T_OPT)
76
0
  {
77
0
    if (len)
78
0
      *len = ansp - start;
79
80
0
    if (p)
81
0
      *p = save;
82
    
83
0
    if (is_last)
84
0
      *is_last = (i == arcount-1);
85
86
0
    ret = start;
87
0
  }
88
0
      else if (is_sign && 
89
0
         i == arcount - 1 && 
90
0
         class == C_ANY && 
91
0
         type == T_TSIG)
92
0
  *is_sign = 1;
93
0
    }
94
  
95
0
  return ret;
96
0
}
97
 
98
99
/* replace == 0 ->don't replace existing option
100
   replace == 1 ->replace existing or add option
101
   replace == 2 ->relpace existing option only.
102
*/
103
size_t add_pseudoheader(struct dns_header *header, size_t plen, size_t out_size, 
104
      int optno, unsigned char *opt, size_t optlen, int set_do, int replace)
105
0
{ 
106
0
  unsigned char *lenp = NULL, *datap = NULL, *p, *udp_len, *buff = NULL;
107
0
  int rdlen = 0, is_sign, is_last;
108
0
  unsigned short flags = set_do ? 0x8000 : 0, rcode = 0;
109
0
  unsigned char *limit = ((unsigned char *)header) + out_size;
110
  
111
0
  p = find_pseudoheader(header, plen, NULL, &udp_len, &is_sign, &is_last);
112
  
113
0
  if (is_sign)
114
0
    return plen;
115
116
0
  if (p)
117
0
    {
118
      /* Existing header */
119
0
      int i;
120
0
      unsigned short code, len;
121
      
122
0
      p = udp_len;
123
124
0
      PUTSHORT(daemon->edns_pktsz, p);
125
0
      GETSHORT(rcode, p);
126
0
      GETSHORT(flags, p);
127
128
0
      if (set_do)
129
0
  {
130
0
    p -= 2;
131
0
    flags |= 0x8000;
132
0
    PUTSHORT(flags, p);
133
0
  }
134
135
0
      lenp = p;
136
0
      GETSHORT(rdlen, p);
137
0
      if (!CHECK_LEN(header, p, plen, rdlen))
138
0
  return plen; /* bad packet */
139
0
      datap = p;
140
141
       /* no option to add */
142
0
      if (optno == 0)
143
0
  return plen;
144
          
145
      /* check if option already there */
146
0
      for (i = 0; i + 4 < rdlen;)
147
0
  {
148
0
    GETSHORT(code, p);
149
0
    GETSHORT(len, p);
150
    
151
    /* malformed option, delete the whole OPT RR and start again. */
152
0
    if (i + 4 + len > rdlen)
153
0
      {
154
0
        rdlen = 0;
155
0
        is_last = 0;
156
0
        break;
157
0
      }
158
    
159
0
    if (code == optno)
160
0
      {
161
0
        if (replace == 0)
162
0
    return plen;
163
164
        /* delete option if we're to replace it. */
165
0
        p -= 4;
166
0
        rdlen -= len + 4;
167
0
        memmove(p, p+len+4, rdlen - i);
168
0
        PUTSHORT(rdlen, lenp);
169
0
        lenp -= 2;
170
0
      }
171
0
    else
172
0
      {
173
0
        p += len;
174
0
        i += len + 4;
175
0
      }
176
0
  }
177
178
      /* If we're going to extend the RR, it has to be the last RR in the packet */
179
0
      if (!is_last)
180
0
  {
181
    /* First, take a copy of the options. */
182
0
    if (rdlen != 0 && (buff = whine_malloc(rdlen)))
183
0
      memcpy(buff, datap, rdlen);       
184
    
185
    /* now, delete OPT RR */
186
0
    rrfilter(header, &plen, RRFILTER_EDNS0);
187
    
188
    /* Now, force addition of a new one */
189
0
    p = NULL;   
190
0
  }
191
0
    }
192
  
193
0
  if (!p)
194
0
    {
195
      /* We are (re)adding the pseudoheader */
196
0
      if (!(p = skip_questions(header, plen)) ||
197
0
    !(p = skip_section(p, 
198
0
           ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount), 
199
0
           header, plen)) ||
200
0
    p + 11 > limit)
201
0
  {
202
0
    free(buff);
203
0
    return plen; /* bad packet */
204
0
  }
205
206
0
      *p++ = 0; /* empty name */
207
0
      PUTSHORT(T_OPT, p);
208
0
      PUTSHORT(daemon->edns_pktsz, p); /* max packet length, 512 if not given in EDNS0 header */
209
0
      PUTSHORT(rcode, p);  /* extended RCODE and version */
210
0
      PUTSHORT(flags, p);  /* DO flag */
211
0
      lenp = p;
212
0
      PUTSHORT(rdlen, p);    /* RDLEN */
213
0
      datap = p;
214
      /* Copy back any options */
215
0
      if (buff)
216
0
  {
217
0
          if (p + rdlen > limit)
218
0
          {
219
0
            free(buff);
220
0
            return plen; /* Too big */
221
0
          }
222
0
    memcpy(p, buff, rdlen);
223
0
    free(buff);
224
0
    p += rdlen;
225
0
  }
226
      
227
      /* Only bump arcount if RR is going to fit */ 
228
0
      if (((ssize_t)optlen) <= (limit - (p + 4)))
229
0
  header->arcount = htons(ntohs(header->arcount) + 1);
230
0
    }
231
  
232
0
  if (((ssize_t)optlen) > (limit - (p + 4)))
233
0
    return plen; /* Too big */
234
  
235
  /* Add new option */
236
0
  if (optno != 0 && replace != 2)
237
0
    {
238
0
      if (p + 4 > limit)
239
0
       return plen; /* Too big */
240
0
      PUTSHORT(optno, p);
241
0
      PUTSHORT(optlen, p);
242
0
      if (p + optlen > limit)
243
0
       return plen; /* Too big */
244
0
      memcpy(p, opt, optlen);
245
0
      p += optlen;  
246
0
      PUTSHORT(p - datap, lenp);
247
0
    }
248
0
  return p - (unsigned char *)header;
249
0
}
250
251
size_t add_do_bit(struct dns_header *header, size_t plen, size_t outlen)
252
0
{
253
0
  return add_pseudoheader(header, plen, outlen, 0, NULL, 0, 1, 0);
254
0
}
255
256
static unsigned char char64(unsigned char c)
257
0
{
258
0
  return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[c & 0x3f];
259
0
}
260
261
static void encoder(unsigned char *in, char *out)
262
0
{
263
0
  out[0] = char64(in[0]>>2);
264
0
  out[1] = char64((in[0]<<4) | (in[1]>>4));
265
0
  out[2] = char64((in[1]<<2) | (in[2]>>6));
266
0
  out[3] = char64(in[2]);
267
0
}
268
269
/* This function needs to call find_mac if any option which requires a MAC address is enabled
270
   and used below. If you add a new MAC consumer, modify this, otherwise your
271
   new EDNS0 option won't work in TCP mode. */
272
void edns0_needs_mac(union mysockaddr *addr, time_t now)
273
0
{
274
0
  if (option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX) || option_bool(OPT_ADD_MAC))
275
0
    find_mac(addr, NULL, 0, now);
276
0
}
277
278
/* OPT_ADD_MAC = MAC is added (if available)
279
   OPT_ADD_MAC + OPT_STRIP_MAC = MAC is replaced, if not available, it is only removed
280
   OPT_STRIP_MAC = MAC is removed
281
   Handle only six byte MACs. */
282
static size_t add_dns_client(struct dns_header *header, size_t plen, size_t outlen,
283
           union mysockaddr *l3, time_t now, int *cacheablep)
284
0
{
285
0
  int replace = 0, maclen = 0;
286
0
  unsigned char mac[DHCP_CHADDR_MAX];
287
0
  char encode[9] = { 0 };
288
0
  char *encoded = encode;
289
  
290
0
  if ((option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX)) && (maclen = find_mac(l3, mac, 1, now)) == 6)
291
0
    {
292
0
      if (option_bool(OPT_STRIP_MAC))
293
0
   replace = 1;
294
0
       *cacheablep = 0;
295
    
296
0
       if (option_bool(OPT_MAC_HEX))
297
0
   encoded = print_mac(mac, maclen);
298
0
       else
299
0
   {
300
0
     encoder(mac, encode);
301
0
     encoder(mac+3, encode+4);
302
0
     encode[8] = 0;
303
0
   }
304
0
    }
305
0
  else if (option_bool(OPT_STRIP_MAC))
306
0
    replace = 2;
307
308
0
  if (replace != 0 || maclen == 6)
309
0
    plen = add_pseudoheader(header, plen, outlen, EDNS0_OPTION_NOMDEVICEID, (unsigned char *)encoded, strlen(encoded), 0, replace);
310
311
0
  return plen;
312
0
}
313
314
315
/* OPT_ADD_MAC = MAC is added (if available)
316
   OPT_ADD_MAC + OPT_STRIP_MAC = MAC is replaced, if not available, it is only removed
317
   OPT_STRIP_MAC = MAC is removed */
318
static size_t add_mac(struct dns_header *header, size_t plen, size_t outlen,
319
          union mysockaddr *l3, time_t now, int *cacheablep)
320
0
{
321
0
  int maclen = 0, replace = 0;
322
0
  unsigned char mac[DHCP_CHADDR_MAX];
323
    
324
0
  if (option_bool(OPT_ADD_MAC) && (maclen = find_mac(l3, mac, 1, now)) != 0)
325
0
    {
326
0
      *cacheablep = 0;
327
0
      if (option_bool(OPT_STRIP_MAC))
328
0
  replace = 1;
329
0
    }
330
0
  else if (option_bool(OPT_STRIP_MAC))
331
0
    replace = 2;
332
  
333
0
  if (replace != 0 || maclen != 0)
334
0
    plen = add_pseudoheader(header, plen, outlen, EDNS0_OPTION_MAC, mac, maclen, 0, replace);
335
336
0
  return plen; 
337
0
}
338
339
struct subnet_opt {
340
  u16 family;
341
  u8 source_netmask, scope_netmask; 
342
  u8 addr[IN6ADDRSZ];
343
};
344
345
static void *get_addrp(union mysockaddr *addr, const short family) 
346
0
{
347
0
  if (family == AF_INET6)
348
0
    return &addr->in6.sin6_addr;
349
350
0
  return &addr->in.sin_addr;
351
0
}
352
353
static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source, int *cacheablep)
354
0
{
355
  /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
356
  
357
0
  int len;
358
0
  void *addrp = NULL;
359
0
  int sa_family = source->sa.sa_family;
360
0
  int cacheable = 0;
361
  
362
0
  opt->source_netmask = 0;
363
0
  opt->scope_netmask = 0;
364
    
365
0
  if (source->sa.sa_family == AF_INET6 && daemon->add_subnet6)
366
0
    {
367
0
      opt->source_netmask = daemon->add_subnet6->mask;
368
0
      if (daemon->add_subnet6->addr_used) 
369
0
  {
370
0
    sa_family = daemon->add_subnet6->addr.sa.sa_family;
371
0
    addrp = get_addrp(&daemon->add_subnet6->addr, sa_family);
372
0
    cacheable = 1;
373
0
  } 
374
0
      else 
375
0
  addrp = &source->in6.sin6_addr;
376
0
    }
377
378
0
  if (source->sa.sa_family == AF_INET && daemon->add_subnet4)
379
0
    {
380
0
      opt->source_netmask = daemon->add_subnet4->mask;
381
0
      if (daemon->add_subnet4->addr_used)
382
0
  {
383
0
    sa_family = daemon->add_subnet4->addr.sa.sa_family;
384
0
    addrp = get_addrp(&daemon->add_subnet4->addr, sa_family);
385
0
    cacheable = 1; /* Address is constant */
386
0
  } 
387
0
  else 
388
0
    addrp = &source->in.sin_addr;
389
0
    }
390
  
391
0
  opt->family = htons(sa_family == AF_INET6 ? 2 : 1);
392
  
393
0
  if (addrp && opt->source_netmask != 0)
394
0
    {
395
0
      len = ((opt->source_netmask - 1) >> 3) + 1;
396
0
      memcpy(opt->addr, addrp, len);
397
0
      if (opt->source_netmask & 7)
398
0
  opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7));
399
0
    }
400
0
  else
401
0
    {
402
0
      cacheable = 1; /* No address ever supplied. */
403
0
      len = 0;
404
0
    }
405
406
0
  if (cacheablep)
407
0
    *cacheablep = cacheable;
408
  
409
0
  return len + 4;
410
0
}
411
 
412
/* OPT_CLIENT_SUBNET = client subnet is added
413
   OPT_CLIENT_SUBNET + OPT_STRIP_ECS = client subnet is replaced
414
   OPT_STRIP_ECS = client subnet is removed */
415
static size_t add_source_addr(struct dns_header *header, size_t plen, size_t outlen,
416
            union mysockaddr *source, int *cacheable)
417
0
{
418
  /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
419
  
420
0
  int replace = 0, len = 0;
421
0
  struct subnet_opt opt;
422
  
423
0
  if (option_bool(OPT_CLIENT_SUBNET))
424
0
    {
425
0
      if (option_bool(OPT_STRIP_ECS))
426
0
  replace = 1;
427
0
      len = calc_subnet_opt(&opt, source, cacheable);
428
0
    }
429
0
  else if (option_bool(OPT_STRIP_ECS))
430
0
    replace = 2;
431
0
  else
432
0
    {
433
0
      unsigned char *pheader;
434
      /* If we still think the data is cacheable, and we're not
435
   messing with EDNS client subnet ourselves, see if the client
436
   sent a client subnet. If so, mark the data as uncacheable */
437
0
      if (*cacheable &&
438
0
    (pheader = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL)) &&
439
0
    !check_source(header, plen, pheader, NULL))
440
0
  *cacheable = 0;
441
      
442
0
      return plen;
443
0
    }
444
  
445
0
  return add_pseudoheader(header, plen, outlen, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, replace);
446
0
}
447
448
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer)
449
0
{
450
  /* Section 9.2, Check that subnet option (if any) in reply matches.
451
     if peer == NULL, this degrades to a check for the existence of and EDNS0 client-subnet option. */
452
  
453
0
  int len, calc_len;
454
0
  struct subnet_opt opt;
455
0
  unsigned char *p;
456
0
  int code, i, rdlen;
457
  
458
0
  if (peer)
459
0
    calc_len = calc_subnet_opt(&opt, peer, NULL);
460
   
461
0
  if (!(p = skip_name(pseudoheader, header, plen, 10)))
462
0
    return 1;
463
  
464
0
  p += 8; /* skip UDP length and RCODE */
465
  
466
0
  GETSHORT(rdlen, p);
467
0
  if (!CHECK_LEN(header, p, plen, rdlen))
468
0
    return 1; /* bad packet */
469
  
470
  /* check if option there */
471
0
  for (i = 0; i + 4 < rdlen; i += len + 4)
472
0
     {
473
0
       GETSHORT(code, p);
474
0
       GETSHORT(len, p);
475
0
       if (i + 4 + len > rdlen)
476
0
   break; /* malformed: option body extends beyond RDATA */
477
0
       if (code == EDNS0_OPTION_CLIENT_SUBNET)
478
0
   {
479
0
     if (peer)
480
0
       {
481
         /* make sure this doesn't mismatch. */
482
0
         opt.scope_netmask = p[3];
483
0
         if (len != calc_len || memcmp(p, &opt, len) != 0)
484
0
     return 0;
485
0
       }
486
0
     else if (((struct subnet_opt *)p)->source_netmask != 0)
487
0
       return 0;
488
0
   }
489
0
       p += len;
490
0
     }
491
  
492
0
  return 1;
493
0
}
494
495
/* See https://docs.umbrella.com/umbrella-api/docs/identifying-dns-traffic for
496
 * detailed information on packet formating.
497
 */
498
0
#define UMBRELLA_VERSION    1
499
#define UMBRELLA_TYPESZ     2
500
501
#define UMBRELLA_ASSET      0x0004
502
#define UMBRELLA_ASSETSZ    sizeof(daemon->umbrella_asset)
503
#define UMBRELLA_ORG        0x0008
504
#define UMBRELLA_ORGSZ      sizeof(daemon->umbrella_org)
505
#define UMBRELLA_IPV4       0x0010
506
#define UMBRELLA_IPV6       0x0020
507
#define UMBRELLA_DEVICE     0x0040
508
0
#define UMBRELLA_DEVICESZ   sizeof(daemon->umbrella_device)
509
510
struct umbrella_opt {
511
  u8 magic[4] ATTRIBUTE_NONSTRING;
512
  u8 version;
513
  u8 flags;
514
  /* We have 4 possible fields since we'll never send both IPv4 and
515
   * IPv6, so using the larger of the two to calculate max buffer size.
516
   * Each field also has a type header.  So the following accounts for
517
   * the type headers and each field size to get a max buffer size.
518
   */
519
  u8 fields[4 * UMBRELLA_TYPESZ + UMBRELLA_ORGSZ + IN6ADDRSZ + UMBRELLA_DEVICESZ + UMBRELLA_ASSETSZ];
520
};
521
522
static size_t add_umbrella_opt(struct dns_header *header, size_t plen, size_t outlen, union mysockaddr *source, int *cacheable)
523
0
{
524
0
  *cacheable = 0;
525
526
0
  struct umbrella_opt opt = {{"ODNS"}, UMBRELLA_VERSION, 0, {0}};
527
0
  u8 *u = &opt.fields[0];
528
0
  int family = source->sa.sa_family;
529
0
  int size = family == AF_INET ? INADDRSZ : IN6ADDRSZ;
530
531
0
  if (daemon->umbrella_org)
532
0
    {
533
0
      PUTSHORT(UMBRELLA_ORG, u);
534
0
      PUTLONG(daemon->umbrella_org, u);
535
0
    }
536
  
537
0
  PUTSHORT(family == AF_INET ? UMBRELLA_IPV4 : UMBRELLA_IPV6, u);
538
0
  memcpy(u, get_addrp(source, family), size);
539
0
  u += size;
540
  
541
0
  if (option_bool(OPT_UMBRELLA_DEVID))
542
0
    {
543
0
      PUTSHORT(UMBRELLA_DEVICE, u);
544
0
      memcpy(u, (char *)&daemon->umbrella_device, UMBRELLA_DEVICESZ);
545
0
      u += UMBRELLA_DEVICESZ;
546
0
    }
547
548
0
  if (daemon->umbrella_asset)
549
0
    {
550
0
      PUTSHORT(UMBRELLA_ASSET, u);
551
0
      PUTLONG(daemon->umbrella_asset, u);
552
0
    }
553
  
554
0
  return add_pseudoheader(header, plen, outlen, EDNS0_OPTION_UMBRELLA, (unsigned char *)&opt, u - (u8 *)&opt, 0, 1);
555
0
}
556
557
/* Set *check_subnet if we add a client subnet option, which needs to checked 
558
   in the reply. Set *cacheable to zero if we add an option which the answer
559
   may depend on. */
560
size_t add_edns0_config(struct dns_header *header, size_t plen, size_t outlen, 
561
      union mysockaddr *source, time_t now, int *cacheable)    
562
0
{
563
0
  *cacheable = 1;
564
  
565
0
  plen  = add_mac(header, plen, outlen, source, now, cacheable);
566
0
  plen = add_dns_client(header, plen, outlen, source, now, cacheable);
567
  
568
0
  if (daemon->dns_client_id)
569
0
    plen = add_pseudoheader(header, plen, outlen, EDNS0_OPTION_NOMCPEID, 
570
0
          (unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1);
571
572
0
  if (option_bool(OPT_UMBRELLA))
573
0
    plen = add_umbrella_opt(header, plen, outlen, source, cacheable);
574
  
575
0
  plen = add_source_addr(header, plen, outlen, source, cacheable);
576
577
0
  return plen;
578
0
}
579