mirror of
https://github.com/pi-hole/FTL.git
synced 2024-10-26 16:52:18 +02:00
89959a953d
Signed-off-by: DL6ER <dl6er@dl6er.de>
368 lines
14 KiB
C
368 lines
14 KiB
C
/* Pi-hole: A black hole for Internet advertisements
|
|
* (c) 2020 Pi-hole, LLC (https://pi-hole.net)
|
|
* Network-wide ad blocking via your own hardware.
|
|
*
|
|
* FTL Engine
|
|
* EDNS parsing routines
|
|
*
|
|
* This file is copyright under the latest version of the EUPL.
|
|
* Please see LICENSE file for your rights under this license. */
|
|
|
|
#define FTLDNS
|
|
#include "dnsmasq/dnsmasq.h"
|
|
#undef __USE_XOPEN
|
|
#include "FTL.h"
|
|
#include "log.h"
|
|
#include "edns0.h"
|
|
#include "config.h"
|
|
#include "datastructure.h"
|
|
#include "shmem.h"
|
|
|
|
// EDNS(0) Client Subnet [Optional, RFC7871]
|
|
#define EDNS0_ECS EDNS0_OPTION_CLIENT_SUBNET
|
|
|
|
// EDN(0) COOKIE [Standard, RFC7873]
|
|
#define EDNS0_COOKIE 10
|
|
|
|
// EDNS(0) MAC address [NOT STANDARDIZED]
|
|
//
|
|
// BYTE encoding, payload size: 6 bytes
|
|
// dnsmasq option: --add-mac
|
|
#define EDNS0_MAC_ADDR_BYTE EDNS0_OPTION_MAC
|
|
// TEXT encoding, payload size: 17 bytes
|
|
// dnsmasq option: --add-mac=text
|
|
#define EDNS0_MAC_ADDR_TEXT EDNS0_OPTION_NOMDEVICEID
|
|
// BASE64 encoding, payload size: 8 bytes
|
|
// dnsmasq option: --add-mac=base64
|
|
#define EDNS0_MAC_ADDR_BASE64 EDNS0_MAC_ADDR_TEXT
|
|
|
|
// EDNS(0) CPE-ID (Common Platform Enumeration Identifier) [NOT STANDARDIZED]
|
|
// Payload: String of any length (can be zero)
|
|
// dnsmasq option: --add-cpe-id=...
|
|
#define EDNS0_CPE_ID EDNS0_OPTION_NOMCPEID
|
|
|
|
void FTL_parse_pseudoheaders(struct dns_header *header, size_t n, union mysockaddr *peer, ednsData *edns)
|
|
{
|
|
int is_sign;
|
|
size_t plen;
|
|
unsigned char *pheader, *sizep;
|
|
|
|
// Extract additional record A.K.A. pseudoheader
|
|
if (!(pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL)))
|
|
return;
|
|
|
|
// Debug logging
|
|
if(config.debug & DEBUG_EDNS0)
|
|
for(unsigned int i = 0; i < plen; i++)
|
|
logg("EDNS(0) pheader[%i] = 0x%02x", i, pheader[i]);
|
|
|
|
// Working pointer
|
|
unsigned char *p = pheader;
|
|
|
|
// RFC 6891 EDNS(0) Extensions 6.1.2. Wire Format
|
|
//
|
|
// An OPT RR has a fixed part and a variable set of options expressed as
|
|
// {attribute, value} pairs. The fixed part holds some DNS metadata,
|
|
// and also a small collection of basic extension elements that we
|
|
// expect to be so popular that it would be a waste of wire space to
|
|
// encode them as {attribute, value} pairs.
|
|
//
|
|
// The fixed part of an OPT RR is structured as follows:
|
|
//
|
|
// +------------+--------------+------------------------------+
|
|
// | Field Name | Field Type | Description |
|
|
// +------------+--------------+------------------------------+
|
|
// | NAME | domain name | MUST be 0 (root domain) |
|
|
if(*p++ != 0)
|
|
return;
|
|
// +------------+--------------+------------------------------+
|
|
// | TYPE | u_int16_t | OPT (41) |
|
|
unsigned short type;
|
|
GETSHORT(type, p);
|
|
if(type != 41)
|
|
return;
|
|
// +------------+--------------+------------------------------+
|
|
// | CLASS | u_int16_t | requestor's UDP payload size |
|
|
unsigned short class;
|
|
GETSHORT(class, p);
|
|
if(config.debug & DEBUG_EDNS0)
|
|
logg("EDNS(0) requestor's UDP payload size: %u bytes", class);
|
|
// +------------+--------------+------------------------------+
|
|
// | TTL | u_int32_t | extended RCODE and flags |
|
|
unsigned long ttl;
|
|
GETLONG(ttl, p);
|
|
// +------------+--------------+------------------------------+
|
|
// | RDLEN | u_int16_t | length of all RDATA |
|
|
unsigned short rdlen;
|
|
GETSHORT(rdlen, p);
|
|
// +------------+--------------+------------------------------+
|
|
// | RDATA | octet stream | {attribute,value} pairs |
|
|
// +------------+--------------+------------------------------+
|
|
|
|
// The variable part of an OPT RR may contain zero or more options in
|
|
// the RDATA. Each option MUST be treated as a bit field. Each option
|
|
// is encoded as:
|
|
//
|
|
// +0 (MSB) +1 (LSB)
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
// 0: | OPTION-CODE |
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
// 2: | OPTION-LENGTH |
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
// 4: | |
|
|
// / OPTION-DATA /
|
|
// / /
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
// RFC 6891 EDNS(0) Extensions 6.1.3. OPT Record TTL Field Use
|
|
//
|
|
// The extended RCODE and flags, which OPT stores in the RR Time to Live
|
|
// (TTL) field, are structured as follows:
|
|
//
|
|
// +0 (MSB) +1 (LSB)
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
// 0: | EXTENDED-RCODE | VERSION |
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
// 2: | DO| Z |
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
//
|
|
// EXTENDED-RCODE
|
|
// Forms the upper 8 bits of extended 12-bit RCODE (together with the
|
|
// 4 bits defined in [RFC1035]. Note that EXTENDED-RCODE value 0
|
|
// indicates that an unextended RCODE is in use (values 0 through
|
|
// 15).
|
|
//
|
|
// VERSION
|
|
// Indicates the implementation level of the setter. Full
|
|
// conformance with this specification is indicated by version '0'.
|
|
// Requestors are encouraged to set this to the lowest implemented
|
|
// level capable of expressing a transaction, to minimise the
|
|
// responder and network load of discovering the greatest common
|
|
// implementation level between requestor and responder. A
|
|
// requestor's version numbering strategy MAY ideally be a run-time
|
|
// configuration option.
|
|
// If a responder does not implement the VERSION level of the
|
|
// request, then it MUST respond with RCODE=BADVERS. All responses
|
|
// MUST be limited in format to the VERSION level of the request, but
|
|
// the VERSION of each response SHOULD be the highest implementation
|
|
// level of the responder. In this way, a requestor will learn the
|
|
// implementation level of a responder as a side effect of every
|
|
// response, including error responses and including RCODE=BADVERS.
|
|
unsigned char edns0_version = (ttl >> 16) % 0xFF;
|
|
if(edns0_version != 0x00)
|
|
return;
|
|
|
|
size_t offset; // The header is 11 bytes before the beginning of OPTION-DATA
|
|
while ((offset = (p - pheader - 11u)) < rdlen && rdlen < UINT16_MAX)
|
|
{
|
|
unsigned short code, optlen;
|
|
GETSHORT(code, p);
|
|
GETSHORT(optlen, p);
|
|
offset += 4;
|
|
|
|
// Avoid buffer overflow due to an malicious packet
|
|
if(offset + optlen > rdlen)
|
|
{
|
|
if(config.debug & DEBUG_EDNS0)
|
|
logg("Found malicious EDNS payload, skipping record.");
|
|
break;
|
|
}
|
|
|
|
// Debug logging
|
|
if(config.debug & DEBUG_EDNS0)
|
|
logg("EDNS(0) code %u, optlen %u (bytes %zu - %zu of %u)",
|
|
code, optlen, offset, offset + optlen, rdlen);
|
|
|
|
if (code == EDNS0_ECS && config.edns0_ecs)
|
|
{
|
|
// EDNS(0) CLIENT SUBNET
|
|
// RFC 7871 Client Subnet in DNS Queries 6. Option Format
|
|
// This protocol uses an EDNS0 [RFC6891] option to include client
|
|
// address information in DNS messages. The option is structured as
|
|
// follows:
|
|
//
|
|
// +0 (MSB) +1 (LSB)
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
// 0: | OPTION-CODE |
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
// 2: | OPTION-LENGTH |
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
// 4: | FAMILY |
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
short family;
|
|
GETSHORT(family, p);
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
// 6: | SOURCE PREFIX-LENGTH | SCOPE PREFIX-LENGTH |
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
unsigned char source_netmask = *p++;
|
|
p++; // We are not interested in the scope prefix-length. It MUST be 0 in queries
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
// 8: | ADDRESS... /
|
|
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
union all_addr addr = {{ 0 }};
|
|
const size_t addrlen = optlen - 4;
|
|
if(family == 1 && addrlen <= sizeof(addr.addr4.s_addr)) // IPv4
|
|
memcpy(&addr.addr4.s_addr, p, addrlen);
|
|
else if(family == 2 && addrlen <= sizeof(addr.addr6.s6_addr)) // IPv6
|
|
memcpy(addr.addr6.s6_addr, p, addrlen);
|
|
else
|
|
continue;
|
|
|
|
// Advance working pointer (we already walked 4 bytes above)
|
|
p += optlen - 4;
|
|
|
|
char ipaddr[ADDRSTRLEN] = { 0 };
|
|
inet_ntop(family == 1 ? AF_INET : AF_INET6, &addr.addr4.s_addr, ipaddr, sizeof(ipaddr));
|
|
|
|
// Only use /32 (IPv4) and /128 (IPv6) addresses
|
|
if(!(family == 1 && source_netmask == 32) &&
|
|
!(family == 2 && source_netmask == 128))
|
|
{
|
|
if(config.debug & DEBUG_EDNS0)
|
|
logg("EDNS(0) CLIENT SUBNET: %s/%u found (IPv%u)",
|
|
ipaddr, source_netmask, family == 1 ? 4 : 6);
|
|
continue;
|
|
}
|
|
|
|
// Copy data to edns struct
|
|
strncpy(edns->client, ipaddr, ADDRSTRLEN);
|
|
edns->client[ADDRSTRLEN-1] = '\0';
|
|
|
|
// Only set the address as useful when it is not the
|
|
// loopback address of the distant machine (127.0.0.0/8 or ::1)
|
|
if((family == 1 && (ntohl(addr.addr4.s_addr) & 0xFF000000) == 0x7F000000) ||
|
|
(family == 2 && IN6_IS_ADDR_LOOPBACK(&addr.addr6)))
|
|
{
|
|
if(config.debug & DEBUG_EDNS0)
|
|
logg("EDNS(0) CLIENT SUBNET: Skipped %s/%u (IPv%u loopback address)",
|
|
ipaddr, source_netmask, family == 1 ? 4 : 6);
|
|
}
|
|
else
|
|
{
|
|
edns->client_set = true;
|
|
if(config.debug & DEBUG_EDNS0)
|
|
logg("EDNS(0) CLIENT SUBNET: %s/%u - OK (IPv%u)",
|
|
ipaddr, source_netmask, family == 1 ? 4 : 6);
|
|
}
|
|
}
|
|
else if(code == EDNS0_COOKIE && optlen == 8)
|
|
{
|
|
// EDNS(0) COOKIE client
|
|
unsigned char client_cookie[8];
|
|
memcpy(client_cookie, p, 8);
|
|
if(config.debug & DEBUG_EDNS0)
|
|
{
|
|
char pretty_client_cookie[8*2 + 1]; // client: fixed length
|
|
char *pp = pretty_client_cookie;
|
|
for(unsigned int j = 0; j < 8; j++)
|
|
pp += sprintf(pp, "%02X", client_cookie[j]);
|
|
logg("EDNS(0) COOKIE (client-only): %s",
|
|
pretty_client_cookie);
|
|
}
|
|
|
|
// Advance working pointer
|
|
p += 8;
|
|
}
|
|
else if(code == EDNS0_COOKIE && optlen >= 16 && optlen <= 40)
|
|
{
|
|
// EDNS(0) COOKIE client + server
|
|
unsigned char client_cookie[8];
|
|
memcpy(client_cookie, p, 8);
|
|
|
|
unsigned short server_cookie_len = optlen - 8;
|
|
unsigned char server_cookie[server_cookie_len];
|
|
memcpy(server_cookie, p + 8u, server_cookie_len);
|
|
if(config.debug & DEBUG_EDNS0)
|
|
{
|
|
char pretty_client_cookie[8*2 + 1]; // client: fixed length
|
|
char *pp = pretty_client_cookie;
|
|
for(unsigned int j = 0; j < 8; j++)
|
|
pp += sprintf(pp, "%02X", client_cookie[j]);
|
|
char pretty_server_cookie[server_cookie_len*2 + 1u]; // server: variable length
|
|
pp = pretty_server_cookie;
|
|
for(unsigned int j = 0; j < server_cookie_len; j++)
|
|
pp += sprintf(pp, "%02X", server_cookie[j]);
|
|
logg("EDNS(0) COOKIE (client + server): %s (client), %s (server, %u bytes)",
|
|
pretty_client_cookie, pretty_server_cookie, server_cookie_len);
|
|
}
|
|
|
|
// Advance working pointer
|
|
p += optlen;
|
|
}
|
|
else if(code == EDNS0_MAC_ADDR_BYTE && optlen == 6)
|
|
{
|
|
// EDNS(0) MAC address (BYTE format)
|
|
memcpy(edns->mac_byte, p, sizeof(edns->mac_byte));
|
|
print_mac(edns->mac_text, (unsigned char*)edns->mac_byte, sizeof(edns->mac_byte));
|
|
edns->mac_set = true;
|
|
if(config.debug & DEBUG_EDNS0)
|
|
logg("EDNS(0) MAC address (BYTE format): %s", edns->mac_text);
|
|
|
|
// Advance working pointer
|
|
p += 6;
|
|
}
|
|
else if(code == EDNS0_MAC_ADDR_TEXT && optlen == 17)
|
|
{
|
|
// EDNS(0) MAC address (TEXT format)
|
|
memcpy(edns->mac_text, p, 17);
|
|
edns->mac_text[17] = '\0';
|
|
if(sscanf(edns->mac_text, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
|
|
&edns->mac_byte[0],
|
|
&edns->mac_byte[1],
|
|
&edns->mac_byte[2],
|
|
&edns->mac_byte[3],
|
|
&edns->mac_byte[4],
|
|
&edns->mac_byte[5]) == 6)
|
|
{
|
|
edns->mac_set = true;
|
|
if(config.debug & DEBUG_EDNS0)
|
|
logg("EDNS(0) MAC address (TEXT format): %s", edns->mac_text);
|
|
}
|
|
else if(config.debug & DEBUG_EDNS0)
|
|
{
|
|
logg(" Received MAC address has invalid format!");
|
|
}
|
|
|
|
// Advance working pointer
|
|
p += 17;
|
|
}
|
|
else if(code == EDNS0_MAC_ADDR_BASE64 && optlen == 8)
|
|
{
|
|
// EDNS(0) MAC address (BASE format)
|
|
if(config.debug & DEBUG_EDNS0)
|
|
logg("EDNS(0) MAC address (BASE64 format): NOT IMPLEMENTED");
|
|
|
|
// Advance working pointer
|
|
p += 8;
|
|
}
|
|
else if(code == EDNS0_CPE_ID && optlen < 256)
|
|
{
|
|
// EDNS(0) CPE-ID, 256 byte arbitrary limit
|
|
unsigned char payload[optlen + 1u]; // variable length
|
|
memcpy(payload, p, optlen);
|
|
payload[optlen] = '\0';
|
|
if(config.debug & DEBUG_EDNS0)
|
|
{
|
|
char pretty_payload[optlen*5 + 1u];
|
|
char *pp = pretty_payload;
|
|
for(unsigned int j = 0; j < optlen; j++)
|
|
pp += sprintf(pp, "0x%02X ", payload[j]);
|
|
pretty_payload[optlen*5 - 1] = '\0'; // Truncate away the trailing whitespace
|
|
logg("EDNS(0) CPE-ID (payload size %u): \"%s\" (%s)",
|
|
optlen, payload, pretty_payload);
|
|
}
|
|
|
|
// Advance working pointer
|
|
p += optlen;
|
|
}
|
|
else
|
|
{
|
|
if(config.debug & DEBUG_EDNS0)
|
|
logg("EDNS(0):n option %u with length %u", code, optlen);
|
|
// Not implemented, skip this record
|
|
|
|
// Advance working pointer
|
|
p += optlen;
|
|
}
|
|
}
|
|
} |