Coverage Report

Created: 2025-08-29 06:42

/src/dnsmasq/src/edns0.c
Line
Count
Source (jump to first uncovered line)
1
/* dnsmasq is Copyright (c) 2000-2025 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, unsigned char *limit, 
104
      int optno, unsigned char *opt, size_t optlen, int set_do, int replace)
105
0
{ 
106
0
  unsigned char *lenp, *datap, *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
110
0
  p = find_pseudoheader(header, plen, NULL, &udp_len, &is_sign, &is_last);
111
  
112
0
  if (is_sign)
113
0
    return plen;
114
115
0
  if (p)
116
0
    {
117
      /* Existing header */
118
0
      int i;
119
0
      unsigned short code, len;
120
      
121
0
      p = udp_len;
122
123
0
      PUTSHORT(daemon->edns_pktsz, p);
124
0
      GETSHORT(rcode, p);
125
0
      GETSHORT(flags, p);
126
127
0
      if (set_do)
128
0
  {
129
0
    p -= 2;
130
0
    flags |= 0x8000;
131
0
    PUTSHORT(flags, p);
132
0
  }
133
134
0
      lenp = p;
135
0
      GETSHORT(rdlen, p);
136
0
      if (!CHECK_LEN(header, p, plen, rdlen))
137
0
  return plen; /* bad packet */
138
0
      datap = p;
139
140
       /* no option to add */
141
0
      if (optno == 0)
142
0
  return plen;
143
          
144
      /* check if option already there */
145
0
      for (i = 0; i + 4 < rdlen;)
146
0
  {
147
0
    GETSHORT(code, p);
148
0
    GETSHORT(len, p);
149
    
150
    /* malformed option, delete the whole OPT RR and start again. */
151
0
    if (i + 4 + len > rdlen)
152
0
      {
153
0
        rdlen = 0;
154
0
        is_last = 0;
155
0
        break;
156
0
      }
157
    
158
0
    if (code == optno)
159
0
      {
160
0
        if (replace == 0)
161
0
    return plen;
162
163
        /* delete option if we're to replace it. */
164
0
        p -= 4;
165
0
        rdlen -= len + 4;
166
0
        memmove(p, p+len+4, rdlen - i);
167
0
        PUTSHORT(rdlen, lenp);
168
0
        lenp -= 2;
169
0
      }
170
0
    else
171
0
      {
172
0
        p += len;
173
0
        i += len + 4;
174
0
      }
175
0
  }
176
177
      /* If we're going to extend the RR, it has to be the last RR in the packet */
178
0
      if (!is_last)
179
0
  {
180
    /* First, take a copy of the options. */
181
0
    if (rdlen != 0 && (buff = whine_malloc(rdlen)))
182
0
      memcpy(buff, datap, rdlen);       
183
    
184
    /* now, delete OPT RR */
185
0
    rrfilter(header, &plen, RRFILTER_EDNS0);
186
    
187
    /* Now, force addition of a new one */
188
0
    p = NULL;   
189
0
  }
190
0
    }
191
  
192
0
  if (!p)
193
0
    {
194
      /* We are (re)adding the pseudoheader */
195
0
      if (!(p = skip_questions(header, plen)) ||
196
0
    !(p = skip_section(p, 
197
0
           ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount), 
198
0
           header, plen)) ||
199
0
    p + 11 > limit)
200
0
  {
201
0
    free(buff);
202
0
    return plen; /* bad packet */
203
0
  }
204
205
0
      *p++ = 0; /* empty name */
206
0
      PUTSHORT(T_OPT, p);
207
0
      PUTSHORT(daemon->edns_pktsz, p); /* max packet length, 512 if not given in EDNS0 header */
208
0
      PUTSHORT(rcode, p);  /* extended RCODE and version */
209
0
      PUTSHORT(flags, p);  /* DO flag */
210
0
      lenp = p;
211
0
      PUTSHORT(rdlen, p);    /* RDLEN */
212
0
      datap = p;
213
      /* Copy back any options */
214
0
      if (buff)
215
0
  {
216
0
          if (p + rdlen > limit)
217
0
          {
218
0
            free(buff);
219
0
            return plen; /* Too big */
220
0
          }
221
0
    memcpy(p, buff, rdlen);
222
0
    free(buff);
223
0
    p += rdlen;
224
0
  }
225
      
226
      /* Only bump arcount if RR is going to fit */ 
227
0
      if (((ssize_t)optlen) <= (limit - (p + 4)))
228
0
  header->arcount = htons(ntohs(header->arcount) + 1);
229
0
    }
230
  
231
0
  if (((ssize_t)optlen) > (limit - (p + 4)))
232
0
    return plen; /* Too big */
233
  
234
  /* Add new option */
235
0
  if (optno != 0 && replace != 2)
236
0
    {
237
0
      if (p + 4 > limit)
238
0
       return plen; /* Too big */
239
0
      PUTSHORT(optno, p);
240
0
      PUTSHORT(optlen, p);
241
0
      if (p + optlen > limit)
242
0
       return plen; /* Too big */
243
0
      memcpy(p, opt, optlen);
244
0
      p += optlen;  
245
0
      PUTSHORT(p - datap, lenp);
246
0
    }
247
0
  return p - (unsigned char *)header;
248
0
}
249
250
size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit)
251
0
{
252
0
  return add_pseudoheader(header, plen, (unsigned char *)limit, 0, NULL, 0, 1, 0);
253
0
}
254
255
static unsigned char char64(unsigned char c)
256
0
{
257
0
  return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[c & 0x3f];
258
0
}
259
260
static void encoder(unsigned char *in, char *out)
261
0
{
262
0
  out[0] = char64(in[0]>>2);
263
0
  out[1] = char64((in[0]<<4) | (in[1]>>4));
264
0
  out[2] = char64((in[1]<<2) | (in[2]>>6));
265
0
  out[3] = char64(in[2]);
266
0
}
267
268
/* OPT_ADD_MAC = MAC is added (if available)
269
   OPT_ADD_MAC + OPT_STRIP_MAC = MAC is replaced, if not available, it is only removed
270
   OPT_STRIP_MAC = MAC is removed */
271
static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit,
272
           union mysockaddr *l3, time_t now, int *cacheablep)
273
0
{
274
0
  int replace = 0, maclen = 0;
275
0
  unsigned char mac[DHCP_CHADDR_MAX];
276
0
  char encode[18]; /* handle 6 byte MACs ONLY */
277
278
0
  if ((option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX)) && (maclen = find_mac(l3, mac, 1, now)) == 6)
279
0
    {
280
0
      if (option_bool(OPT_STRIP_MAC))
281
0
   replace = 1;
282
0
       *cacheablep = 0;
283
    
284
0
       if (option_bool(OPT_MAC_HEX))
285
0
   print_mac(encode, mac, maclen);
286
0
       else
287
0
   {
288
0
     encoder(mac, encode);
289
0
     encoder(mac+3, encode+4);
290
0
     encode[8] = 0;
291
0
   }
292
0
    }
293
0
  else if (option_bool(OPT_STRIP_MAC))
294
0
    replace = 2;
295
296
0
  if (replace != 0 || maclen == 6)
297
0
    plen = add_pseudoheader(header, plen, limit, EDNS0_OPTION_NOMDEVICEID, (unsigned char *)encode, strlen(encode), 0, replace);
298
299
0
  return plen;
300
0
}
301
302
303
/* OPT_ADD_MAC = MAC is added (if available)
304
   OPT_ADD_MAC + OPT_STRIP_MAC = MAC is replaced, if not available, it is only removed
305
   OPT_STRIP_MAC = MAC is removed */
306
static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit,
307
          union mysockaddr *l3, time_t now, int *cacheablep)
308
0
{
309
0
  int maclen = 0, replace = 0;
310
0
  unsigned char mac[DHCP_CHADDR_MAX];
311
    
312
0
  if (option_bool(OPT_ADD_MAC) && (maclen = find_mac(l3, mac, 1, now)) != 0)
313
0
    {
314
0
      *cacheablep = 0;
315
0
      if (option_bool(OPT_STRIP_MAC))
316
0
  replace = 1;
317
0
    }
318
0
  else if (option_bool(OPT_STRIP_MAC))
319
0
    replace = 2;
320
  
321
0
  if (replace != 0 || maclen != 0)
322
0
    plen = add_pseudoheader(header, plen, limit, EDNS0_OPTION_MAC, mac, maclen, 0, replace);
323
324
0
  return plen; 
325
0
}
326
327
struct subnet_opt {
328
  u16 family;
329
  u8 source_netmask, scope_netmask; 
330
  u8 addr[IN6ADDRSZ];
331
};
332
333
static void *get_addrp(union mysockaddr *addr, const short family) 
334
0
{
335
0
  if (family == AF_INET6)
336
0
    return &addr->in6.sin6_addr;
337
338
0
  return &addr->in.sin_addr;
339
0
}
340
341
static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source, int *cacheablep)
342
0
{
343
  /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
344
  
345
0
  int len;
346
0
  void *addrp = NULL;
347
0
  int sa_family = source->sa.sa_family;
348
0
  int cacheable = 0;
349
  
350
0
  opt->source_netmask = 0;
351
0
  opt->scope_netmask = 0;
352
    
353
0
  if (source->sa.sa_family == AF_INET6 && daemon->add_subnet6)
354
0
    {
355
0
      opt->source_netmask = daemon->add_subnet6->mask;
356
0
      if (daemon->add_subnet6->addr_used) 
357
0
  {
358
0
    sa_family = daemon->add_subnet6->addr.sa.sa_family;
359
0
    addrp = get_addrp(&daemon->add_subnet6->addr, sa_family);
360
0
    cacheable = 1;
361
0
  } 
362
0
      else 
363
0
  addrp = &source->in6.sin6_addr;
364
0
    }
365
366
0
  if (source->sa.sa_family == AF_INET && daemon->add_subnet4)
367
0
    {
368
0
      opt->source_netmask = daemon->add_subnet4->mask;
369
0
      if (daemon->add_subnet4->addr_used)
370
0
  {
371
0
    sa_family = daemon->add_subnet4->addr.sa.sa_family;
372
0
    addrp = get_addrp(&daemon->add_subnet4->addr, sa_family);
373
0
    cacheable = 1; /* Address is constant */
374
0
  } 
375
0
  else 
376
0
    addrp = &source->in.sin_addr;
377
0
    }
378
  
379
0
  opt->family = htons(sa_family == AF_INET6 ? 2 : 1);
380
  
381
0
  if (addrp && opt->source_netmask != 0)
382
0
    {
383
0
      len = ((opt->source_netmask - 1) >> 3) + 1;
384
0
      memcpy(opt->addr, addrp, len);
385
0
      if (opt->source_netmask & 7)
386
0
  opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7));
387
0
    }
388
0
  else
389
0
    {
390
0
      cacheable = 1; /* No address ever supplied. */
391
0
      len = 0;
392
0
    }
393
394
0
  if (cacheablep)
395
0
    *cacheablep = cacheable;
396
  
397
0
  return len + 4;
398
0
}
399
 
400
/* OPT_CLIENT_SUBNET = client subnet is added
401
   OPT_CLIENT_SUBNET + OPT_STRIP_ECS = client subnet is replaced
402
   OPT_STRIP_ECS = client subnet is removed */
403
static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit,
404
            union mysockaddr *source, int *cacheable)
405
0
{
406
  /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
407
  
408
0
  int replace = 0, len = 0;
409
0
  struct subnet_opt opt;
410
  
411
0
  if (option_bool(OPT_CLIENT_SUBNET))
412
0
    {
413
0
      if (option_bool(OPT_STRIP_ECS))
414
0
  replace = 1;
415
0
      len = calc_subnet_opt(&opt, source, cacheable);
416
0
    }
417
0
  else if (option_bool(OPT_STRIP_ECS))
418
0
    replace = 2;
419
0
  else
420
0
    {
421
0
      unsigned char *pheader;
422
      /* If we still think the data is cacheable, and we're not
423
   messing with EDNS client subnet ourselves, see if the client
424
   sent a client subnet. If so, mark the data as uncacheable */
425
0
      if (*cacheable &&
426
0
    (pheader = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL)) &&
427
0
    !check_source(header, plen, pheader, NULL))
428
0
  *cacheable = 0;
429
      
430
0
      return plen;
431
0
    }
432
  
433
0
  return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, replace);
434
0
}
435
436
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer)
437
0
{
438
  /* Section 9.2, Check that subnet option (if any) in reply matches.
439
     if peer == NULL, this degrades to a check for the existence of and EDNS0 client-subnet option. */
440
  
441
0
  int len, calc_len;
442
0
  struct subnet_opt opt;
443
0
  unsigned char *p;
444
0
  int code, i, rdlen;
445
  
446
0
  if (peer)
447
0
    calc_len = calc_subnet_opt(&opt, peer, NULL);
448
   
449
0
  if (!(p = skip_name(pseudoheader, header, plen, 10)))
450
0
    return 1;
451
  
452
0
  p += 8; /* skip UDP length and RCODE */
453
  
454
0
  GETSHORT(rdlen, p);
455
0
  if (!CHECK_LEN(header, p, plen, rdlen))
456
0
    return 1; /* bad packet */
457
  
458
  /* check if option there */
459
0
  for (i = 0; i + 4 < rdlen; i += len + 4)
460
0
     {
461
0
       GETSHORT(code, p);
462
0
       GETSHORT(len, p);
463
0
       if (code == EDNS0_OPTION_CLIENT_SUBNET)
464
0
   {
465
0
     if (peer)
466
0
       {
467
         /* make sure this doesn't mismatch. */
468
0
         opt.scope_netmask = p[3];
469
0
         if (len != calc_len || memcmp(p, &opt, len) != 0)
470
0
     return 0;
471
0
       }
472
0
     else if (((struct subnet_opt *)p)->source_netmask != 0)
473
0
       return 0;
474
0
   }
475
0
       p += len;
476
0
     }
477
  
478
0
  return 1;
479
0
}
480
481
/* See https://docs.umbrella.com/umbrella-api/docs/identifying-dns-traffic for
482
 * detailed information on packet formating.
483
 */
484
0
#define UMBRELLA_VERSION    1
485
#define UMBRELLA_TYPESZ     2
486
487
#define UMBRELLA_ASSET      0x0004
488
#define UMBRELLA_ASSETSZ    sizeof(daemon->umbrella_asset)
489
#define UMBRELLA_ORG        0x0008
490
#define UMBRELLA_ORGSZ      sizeof(daemon->umbrella_org)
491
#define UMBRELLA_IPV4       0x0010
492
#define UMBRELLA_IPV6       0x0020
493
#define UMBRELLA_DEVICE     0x0040
494
0
#define UMBRELLA_DEVICESZ   sizeof(daemon->umbrella_device)
495
496
struct umbrella_opt {
497
  u8 magic[4] ATTRIBUTE_NONSTRING;
498
  u8 version;
499
  u8 flags;
500
  /* We have 4 possible fields since we'll never send both IPv4 and
501
   * IPv6, so using the larger of the two to calculate max buffer size.
502
   * Each field also has a type header.  So the following accounts for
503
   * the type headers and each field size to get a max buffer size.
504
   */
505
  u8 fields[4 * UMBRELLA_TYPESZ + UMBRELLA_ORGSZ + IN6ADDRSZ + UMBRELLA_DEVICESZ + UMBRELLA_ASSETSZ];
506
};
507
508
static size_t add_umbrella_opt(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable)
509
0
{
510
0
  *cacheable = 0;
511
512
0
  struct umbrella_opt opt = {{"ODNS"}, UMBRELLA_VERSION, 0, {0}};
513
0
  u8 *u = &opt.fields[0];
514
0
  int family = source->sa.sa_family;
515
0
  int size = family == AF_INET ? INADDRSZ : IN6ADDRSZ;
516
517
0
  if (daemon->umbrella_org)
518
0
    {
519
0
      PUTSHORT(UMBRELLA_ORG, u);
520
0
      PUTLONG(daemon->umbrella_org, u);
521
0
    }
522
  
523
0
  PUTSHORT(family == AF_INET ? UMBRELLA_IPV4 : UMBRELLA_IPV6, u);
524
0
  memcpy(u, get_addrp(source, family), size);
525
0
  u += size;
526
  
527
0
  if (option_bool(OPT_UMBRELLA_DEVID))
528
0
    {
529
0
      PUTSHORT(UMBRELLA_DEVICE, u);
530
0
      memcpy(u, (char *)&daemon->umbrella_device, UMBRELLA_DEVICESZ);
531
0
      u += UMBRELLA_DEVICESZ;
532
0
    }
533
534
0
  if (daemon->umbrella_asset)
535
0
    {
536
0
      PUTSHORT(UMBRELLA_ASSET, u);
537
0
      PUTLONG(daemon->umbrella_asset, u);
538
0
    }
539
  
540
0
  return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_UMBRELLA, (unsigned char *)&opt, u - (u8 *)&opt, 0, 1);
541
0
}
542
543
/* Set *check_subnet if we add a client subnet option, which needs to checked 
544
   in the reply. Set *cacheable to zero if we add an option which the answer
545
   may depend on. */
546
size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, 
547
      union mysockaddr *source, time_t now, int *cacheable)    
548
0
{
549
0
  *cacheable = 1;
550
  
551
0
  plen  = add_mac(header, plen, limit, source, now, cacheable);
552
0
  plen = add_dns_client(header, plen, limit, source, now, cacheable);
553
  
554
0
  if (daemon->dns_client_id)
555
0
    plen = add_pseudoheader(header, plen, limit, EDNS0_OPTION_NOMCPEID, 
556
0
          (unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1);
557
558
0
  if (option_bool(OPT_UMBRELLA))
559
0
    plen = add_umbrella_opt(header, plen, limit, source, cacheable);
560
  
561
0
  plen = add_source_addr(header, plen, limit, source, cacheable);
562
563
0
  return plen;
564
0
}