Commit 7bcfef43 authored by Daniel Salzman's avatar Daniel Salzman

kdig: add support for EDNS client subnet (+client)

refs #252
parent 4193af14
......@@ -178,6 +178,9 @@ Use EDNS version (default is 0).
Disable IDN transformation to ASCII and vice versa.
IDNA2003 support depends on libidn availability during project building!
.TP
.BI +client= SUBN
Set EDNS client subnet SUBN=IP/prefix.
.TP
.BI +time= T
Set wait for reply interval in seconds (default is 5 seconds).
This timeout applies to each query try.
......
......@@ -42,6 +42,16 @@
*/
#define KNOT_RR_HEADER_SIZE 10
/*!
* \brief Address family numbers.
*
* http://www.iana.org/assignments/address-family-numbers/address-family-numbers.xml
*/
typedef enum {
KNOT_ADDR_FAMILY_IPV4 = 1, /*!< IP version 4. */
KNOT_ADDR_FAMILY_IPV6 = 2 /*!< IP version 6. */
} knot_addr_family_t;
/*!
* \brief DNS operation codes (OPCODEs).
*
......
......@@ -23,15 +23,26 @@
#include "libknot/common.h"
#include "common/descriptor.h"
#include "common/debug.h"
#include "common/sockaddr.h"
/*! \brief Some implementation-related constants. */
enum knot_edns_private_consts {
/*! \brief Mask for the DO bit (machine byte order) */
KNOT_EDNS_DO_MASK = (uint32_t)(1 << 15),
/*! \brief Offset of Extended RCODE field in TTL (network byte order). */
KNOT_EDNS_OFFSET_RCODE = 0,
/*! \brief Offset of the Version field in TTL (network byte order). */
KNOT_EDNS_OFFSET_VERSION = 1
/*! \brief Bit mask for DO bit. */
EDNS_DO_MASK = (uint32_t)(1 << 15),
/*! \brief Byte offset of the extended RCODE field in TTL. */
EDNS_OFFSET_RCODE = 0,
/*! \brief Byte offset of the version field in TTL. */
EDNS_OFFSET_VERSION = 1,
/*! \brief Byte offset of the family field in option data. */
EDNS_OFFSET_CLIENT_SUBNET_FAMILY = 0,
/*! \brief Byte offset of the source mask field in option data. */
EDNS_OFFSET_CLIENT_SUBNET_SRC_MASK = 2,
/*! \brief Byte offset of the destination mask field in option data. */
EDNS_OFFSET_CLIENT_SUBNET_DST_MASK = 3,
/*! \brief Byte offset of the address field in option data. */
EDNS_OFFSET_CLIENT_SUBNET_ADDR = 4,
};
/*----------------------------------------------------------------------------*/
......@@ -104,7 +115,7 @@ uint8_t knot_edns_get_ext_rcode(const knot_rrset_t *opt_rr)
knot_wire_write_u32(ttl_ptr, knot_rrset_ttl(opt_rr));
uint8_t rcode;
memcpy(&rcode, ttl_ptr + KNOT_EDNS_OFFSET_RCODE, sizeof(uint8_t));
memcpy(&rcode, ttl_ptr + EDNS_OFFSET_RCODE, sizeof(uint8_t));
return rcode;
}
......@@ -131,7 +142,7 @@ static void set_value_to_ttl(knot_rrset_t *opt_rr, size_t offset, uint8_t value)
void knot_edns_set_ext_rcode(knot_rrset_t *opt_rr, uint8_t ext_rcode)
{
assert(opt_rr != NULL);
set_value_to_ttl(opt_rr, KNOT_EDNS_OFFSET_RCODE, ext_rcode);
set_value_to_ttl(opt_rr, EDNS_OFFSET_RCODE, ext_rcode);
}
/*----------------------------------------------------------------------------*/
......@@ -146,7 +157,7 @@ uint8_t knot_edns_get_version(const knot_rrset_t *opt_rr)
knot_wire_write_u32(ttl_ptr, knot_rrset_ttl(opt_rr));
uint8_t version;
memcpy(&version, ttl_ptr + KNOT_EDNS_OFFSET_VERSION, sizeof(uint8_t));
memcpy(&version, ttl_ptr + EDNS_OFFSET_VERSION, sizeof(uint8_t));
return version;
}
......@@ -156,7 +167,7 @@ uint8_t knot_edns_get_version(const knot_rrset_t *opt_rr)
void knot_edns_set_version(knot_rrset_t *opt_rr, uint8_t version)
{
assert(opt_rr != NULL);
set_value_to_ttl(opt_rr, KNOT_EDNS_OFFSET_VERSION, version);
set_value_to_ttl(opt_rr, EDNS_OFFSET_VERSION, version);
}
/*----------------------------------------------------------------------------*/
......@@ -165,7 +176,7 @@ bool knot_edns_do(const knot_rrset_t *opt_rr)
{
assert(opt_rr != NULL);
return knot_rrset_ttl(opt_rr) & KNOT_EDNS_DO_MASK;
return knot_rrset_ttl(opt_rr) & EDNS_DO_MASK;
}
/*----------------------------------------------------------------------------*/
......@@ -177,7 +188,7 @@ void knot_edns_set_do(knot_rrset_t *opt_rr)
// Read the TTL
uint32_t ttl = knot_rrset_ttl(opt_rr);
// Set the DO bit
ttl |= KNOT_EDNS_DO_MASK;
ttl |= EDNS_DO_MASK;
// Store the TTL to the RDATA
knot_rdata_set_ttl(knot_rdataset_at(&opt_rr->rrs, 0), ttl);
}
......@@ -305,3 +316,70 @@ bool knot_edns_check_record(knot_rrset_t *opt_rr)
*/
return pos == rdlength;
}
/*----------------------------------------------------------------------------*/
int knot_edns_client_subnet_create(const knot_addr_family_t family,
const uint8_t *addr,
const uint16_t addr_len,
uint8_t src_mask,
uint8_t dst_mask,
uint8_t *data,
uint16_t *data_len)
{
if (addr == NULL || data == NULL || data_len == NULL) {
return KNOT_EINVAL;
}
uint8_t addr_prefix_len = (src_mask + 7) / 8;
uint8_t last_byte_mask = 0xFF << (8 - (src_mask % 8));
uint16_t total = sizeof(uint16_t) + 2 * sizeof(uint8_t) + addr_prefix_len;
if (*data_len < total) {
return KNOT_ESPACE;
}
knot_wire_write_u16(data + EDNS_OFFSET_CLIENT_SUBNET_FAMILY, family);
data[EDNS_OFFSET_CLIENT_SUBNET_SRC_MASK] = src_mask;
data[EDNS_OFFSET_CLIENT_SUBNET_DST_MASK] = dst_mask;
memcpy(data + EDNS_OFFSET_CLIENT_SUBNET_ADDR, addr, addr_prefix_len);
// Zeroize trailing bits in the last byte.
if (addr_prefix_len > 0) {
data[EDNS_OFFSET_CLIENT_SUBNET_ADDR + addr_prefix_len - 1] &=
last_byte_mask;
}
*data_len = total;
return KNOT_EOK;
}
int knot_edns_client_subnet_parse(const uint8_t *data,
const uint16_t data_len,
knot_addr_family_t *family,
uint8_t *addr,
uint16_t *addr_len,
uint8_t *src_mask,
uint8_t *dst_mask)
{
if (data == NULL || family == NULL || addr == NULL ||
addr_len == NULL || src_mask == NULL || dst_mask == NULL) {
return KNOT_EINVAL;
}
int rest = data_len - sizeof(uint16_t) - 2 * sizeof(uint8_t);
if (rest < 0 || *addr_len < rest) {
return KNOT_ESPACE;
}
*family = knot_wire_read_u16(data + EDNS_OFFSET_CLIENT_SUBNET_FAMILY);
*src_mask = data[EDNS_OFFSET_CLIENT_SUBNET_SRC_MASK];
*dst_mask = data[EDNS_OFFSET_CLIENT_SUBNET_DST_MASK];
memcpy(addr, data + EDNS_OFFSET_CLIENT_SUBNET_ADDR, rest);
*addr_len = rest;
return KNOT_EOK;
}
......@@ -34,22 +34,29 @@
/* Forward declaration. */
struct knot_packet;
/*! \brief Various constants related to EDNS. */
/*! \brief Constants related to EDNS. */
enum knot_edns_const {
/*! \brief Supported EDNS version. */
KNOT_EDNS_VERSION = 0,
/*! \brief Minimal UDP payload with EDNS enabled. */
KNOT_EDNS_MIN_UDP_PAYLOAD = 512,
/*! \brief Minimal payload when using DNSSEC (RFC4035/sec.3) */
KNOT_EDNS_MIN_UDP_PAYLOAD = 512,
/*! \brief Minimal payload when using DNSSEC (RFC4035/sec.3). */
KNOT_EDNS_MIN_DNSSEC_PAYLOAD = 1220,
/*! \brief Maximal UDP payload with EDNS enabled. */
KNOT_EDNS_MAX_UDP_PAYLOAD = 4096,
/*! \brief Supported EDNS version. */
KNOT_EDNS_VERSION = 0,
/*! \brief NSID option code. */
KNOT_EDNS_OPTION_NSID = 3,
KNOT_EDNS_MAX_UDP_PAYLOAD = 4096,
/*! \brief Minimum size of EDNS OPT RR in wire format. */
KNOT_EDNS_MIN_SIZE = 11,
KNOT_EDNS_MIN_SIZE = 11,
/*! \brief EDNS OPT header size. */
KNOT_EDNS_OPTION_HDRLEN = 2 * sizeof(uint16_t)
KNOT_EDNS_OPTION_HDRLEN = 4,
/*! \brief Maximal edns client subnet data size (IPv6). */
KNOT_EDNS_MAX_OPTION_CLIENT_SUBNET = 20,
/*! \brief NSID option code. */
KNOT_EDNS_OPTION_NSID = 3,
/*! \brief EDNS client subnet option code. */
KNOT_EDNS_OPTION_CLIENT_SUBNET = 8
};
/*!
......@@ -233,4 +240,49 @@ bool knot_edns_has_nsid(const knot_rrset_t *opt_rr);
*/
bool knot_edns_check_record(knot_rrset_t *opt_rr);
/*!
* \brief Creates client subnet wire data.
*
* \param family Address family.
* \param addr Binary representation of IP address.
* \param addr_len Length of the address.
* \param src_mask Source mask.
* \param dst_mask Destination mask.
* \param data Output data buffer.
* \param data_len Size of output data buffer/written data.
*
* \return KNOT_EOK
* \return KNOT_EINVAL
* \return KNOT_ESPACE
*/
int knot_edns_client_subnet_create(const knot_addr_family_t family,
const uint8_t *addr,
const uint16_t addr_len,
uint8_t src_mask,
uint8_t dst_mask,
uint8_t *data,
uint16_t *data_len);
/*!
* \brief Parses client subnet wire data.
*
* \param data Input data buffer.
* \param data_len Length of input data buffer.
* \param family Address family.
* \param addr Binary representation of IP address.
* \param addr_len Size of address buffer/written address data.
* \param src_mask Source mask.
* \param dst_mask Destination mask.
*
* \return KNOT_EOK
* \return KNOT_EINVAL
* \return KNOT_ESPACE
*/
int knot_edns_client_subnet_parse(const uint8_t *data,
const uint16_t data_len,
knot_addr_family_t *family,
uint8_t *addr,
uint16_t *addr_len,
uint8_t *src_mask,
uint8_t *dst_mask);
/*! @} */
......@@ -18,12 +18,14 @@
#include <stdlib.h> // free
#include <time.h> // localtime_r
#include <arpa/inet.h> // inet_ntop
#include "libknot/libknot.h"
#include "common/lists.h" // list
#include "common/print.h" // txt_print
#include "common/errcode.h" // KNOT_EOK
#include "common/descriptor.h" // KNOT_RRTYPE_
#include "common/sockaddr.h" // IPV4_PREFIXLEN
#include "common/strlcat.h" // strlcat
#include "utils/common/msg.h" // WARN
#include "utils/common/params.h" // params_t
......@@ -179,6 +181,40 @@ static void print_footer(const size_t total_len,
}
}
static void print_edns_client_subnet(const uint8_t *data, const uint16_t len)
{
struct in_addr addr4;
struct in6_addr addr6;
knot_addr_family_t family;
uint8_t src_mask, dst_mask;
uint8_t addr[IPV6_PREFIXLEN / 8] = { 0 };
uint16_t addr_len = sizeof(addr);
char addr_str[128] = { '\0' };
int ret = knot_edns_client_subnet_parse(data, len, &family, addr,
&addr_len, &src_mask, &dst_mask);
if (ret != KNOT_EOK) {
printf("\n");
return;
}
switch (family) {
case KNOT_ADDR_FAMILY_IPV4:
memcpy(&(addr4.s_addr), addr, IPV4_PREFIXLEN / 8);
inet_ntop(AF_INET, &addr4, addr_str, sizeof(addr_str));
break;
case KNOT_ADDR_FAMILY_IPV6:
memcpy(&(addr6.s6_addr), addr, IPV6_PREFIXLEN / 8);
inet_ntop(AF_INET6, &addr6, addr_str, sizeof(addr_str));
break;
default:
printf("unsupported address family\n");
return;
}
printf("%s/%u/%u\n", addr_str, src_mask, dst_mask);
}
static void print_section_opt(const knot_rrset_t *rr)
{
uint8_t ext_rcode_id = knot_edns_get_ext_rcode(rr);
......@@ -208,12 +244,20 @@ static void print_section_opt(const knot_rrset_t *rr)
uint16_t opt_len = knot_wire_read_u16(data + pos + 2);
uint8_t *opt_data = data + pos + 4;
if (opt_code == KNOT_EDNS_OPTION_NSID) {
switch (opt_code) {
case KNOT_EDNS_OPTION_NSID:
printf(";; NSID: ");
short_hex_print(opt_data, opt_len);
printf(";; : ");
txt_print(opt_data, opt_len);
} else {
if (opt_len > 0) {
printf(";; : ");
txt_print(opt_data, opt_len);
}
break;
case KNOT_EDNS_OPTION_CLIENT_SUBNET:
printf(";; CLIENT-SUBNET: ");
print_edns_client_subnet(opt_data, opt_len);
break;
default:
printf(";; Option (%u): ", opt_code);
short_hex_print(opt_data, opt_len);
}
......
......@@ -201,9 +201,6 @@ static void process_dnstap(const query_t *query)
static int add_query_edns(knot_pkt_t *packet, const query_t *query, int max_size)
{
assert(packet);
assert(query);
/* Initialize OPT RR. */
knot_rrset_t opt_rr;
int ret = knot_edns_init(&opt_rr, max_size, 0,
......@@ -226,6 +223,29 @@ static int add_query_edns(knot_pkt_t *packet, const query_t *query, int max_size
}
}
/* Append EDNS-client-subnet. */
if (query->subnet != NULL) {
uint8_t data[KNOT_EDNS_MAX_OPTION_CLIENT_SUBNET] = { 0 };
uint16_t data_len = sizeof(data);
ret = knot_edns_client_subnet_create(query->subnet->family,
query->subnet->addr,
query->subnet->addr_len,
query->subnet->netmask,
0, data, &data_len);
if (ret != KNOT_EOK) {
knot_rrset_clear(&opt_rr, &packet->mm);
return ret;
}
ret = knot_edns_add_option(&opt_rr, KNOT_EDNS_OPTION_CLIENT_SUBNET,
data_len, data, &packet->mm);
if (ret != KNOT_EOK) {
knot_rrset_clear(&opt_rr, &packet->mm);
return ret;
}
}
/* Add prepared OPT to packet. */
ret = knot_pkt_put(packet, COMPR_HINT_NONE, &opt_rr, KNOT_PF_FREE);
if (ret != KNOT_EOK) {
......@@ -235,6 +255,12 @@ static int add_query_edns(knot_pkt_t *packet, const query_t *query, int max_size
return ret;
}
static bool use_edns(const query_t *query)
{
return query->flags.do_flag || query->nsid || query->edns > -1 ||
query->subnet != NULL;
}
static knot_pkt_t* create_query_packet(const query_t *query)
{
knot_pkt_t *packet;
......@@ -246,8 +272,7 @@ static knot_pkt_t* create_query_packet(const query_t *query)
if (get_socktype(query->protocol, query->type_num)
== SOCK_STREAM) {
max_size = MAX_PACKET_SIZE;
} else if (query->flags.do_flag || query->nsid ||
query->edns > -1) {
} else if (use_edns(query)) {
max_size = DEFAULT_EDNS_SIZE;
} else {
max_size = DEFAULT_UDP_SIZE;
......@@ -354,8 +379,7 @@ static knot_pkt_t* create_query_packet(const query_t *query)
knot_pkt_begin(packet, KNOT_ADDITIONAL);
// Create EDNS section if required.
if (query->udp_size > 0 || query->flags.do_flag || query->nsid ||
query->edns > -1) {
if (query->udp_size > 0 || use_edns(query)) {
int ret = add_query_edns(packet, query, max_size);
if (ret != KNOT_EOK) {
ERR("can't set up EDNS section\n");
......
......@@ -21,10 +21,12 @@
#include <getopt.h> // getopt
#include <stdlib.h> // free
#include <locale.h> // setlocale
#include <arpa/inet.h> // inet_pton
#include "common/lists.h" // list
#include "common/errcode.h" // KNOT_EOK
#include "common/descriptor.h" // KNOT_CLASS_IN
#include "common/sockaddr.h" // IPV4_PREFIXLEN
#include "utils/common/msg.h" // WARN
#include "utils/common/params.h" // parse_class
#include "utils/common/resolv.h" // get_nameservers
......@@ -610,6 +612,68 @@ static int opt_noedns(const char *arg, void *query)
return KNOT_EOK;
}
static int opt_client(const char *arg, void *query)
{
query_t *q = query;
struct in_addr addr4;
struct in6_addr addr6;
char *sep = NULL;
const size_t arg_len = strlen(arg);
const char *arg_end = arg + arg_len;
char *addr = NULL;
size_t addr_len = 0;
subnet_t *subnet = calloc(sizeof(subnet_t), 1);
// Separate address and network mask.
if ((sep = index(arg, '/')) != NULL) {
addr_len = sep - arg;
} else {
addr_len = arg_len;
}
// Check IP address.
addr = strndup(arg, addr_len);
if (inet_pton(AF_INET, addr, &addr4) == 1) {
subnet->family = KNOT_ADDR_FAMILY_IPV4;
memcpy(subnet->addr, &(addr4.s_addr), IPV4_PREFIXLEN / 8);
subnet->addr_len = IPV4_PREFIXLEN / 8;
subnet->netmask = IPV4_PREFIXLEN;
} else if (inet_pton(AF_INET6, addr, &addr6) == 1) {
subnet->family = KNOT_ADDR_FAMILY_IPV6;
memcpy(subnet->addr, &(addr6.s6_addr), IPV6_PREFIXLEN / 8);
subnet->addr_len = IPV6_PREFIXLEN / 8;
subnet->netmask = IPV6_PREFIXLEN;
} else {
free(addr);
free(subnet);
ERR("invalid address +client=%s\n", arg);
return KNOT_EINVAL;
}
free(addr);
// Parse network mask.
if (arg + addr_len < arg_end) {
char *end;
arg += addr_len + 1;
unsigned long num = strtoul(arg, &end, 10);
if (end == arg || *end != '\0' || num > subnet->netmask) {
free(subnet);
ERR("invalid network mask +client=%s\n", arg);
return KNOT_EINVAL;
}
subnet->netmask = num;
}
free(q->subnet);
q->subnet = subnet;
return KNOT_EOK;
}
static int opt_time(const char *arg, void *query)
{
query_t *q = query;
......@@ -733,15 +797,17 @@ static const param_t dig_opts2[] = {
{ "ignore", ARG_NONE, opt_ignore },
{ "noignore", ARG_NONE, opt_noignore },
/* "idn" doesn't work since it must be called before query creation. */
{ "noidn", ARG_NONE, opt_noidn },
{ "nsid", ARG_NONE, opt_nsid },
{ "nonsid", ARG_NONE, opt_nonsid },
{ "edns", ARG_OPTIONAL, opt_edns },
{ "noedns", ARG_NONE, opt_noedns },
/* "idn" doesn't work since it must be called before query creation. */
{ "noidn", ARG_NONE, opt_noidn },
{ "client", ARG_REQUIRED, opt_client },
{ "time", ARG_REQUIRED, opt_time },
{ "retry", ARG_REQUIRED, opt_retry },
......@@ -794,6 +860,7 @@ query_t* query_create(const char *owner, const query_t *conf)
query->idn = true;
query->nsid = false;
query->edns = -1;
query->subnet = NULL;
#if USE_DNSTAP
query->dt_reader = NULL;
query->dt_writer = NULL;
......@@ -828,6 +895,16 @@ query_t* query_create(const char *owner, const query_t *conf)
query->idn = conf->idn;
query->nsid = conf->nsid;
query->edns = conf->edns;
if (conf->subnet != NULL) {
query->subnet = malloc(sizeof(subnet_t));
if (query->subnet == NULL) {
query_free(query);
return NULL;
}
*(query->subnet) = *(conf->subnet);
} else {
query->subnet = NULL;
}
#if USE_DNSTAP
query->dt_reader = conf->dt_reader;
query->dt_writer = conf->dt_writer;
......@@ -887,6 +964,7 @@ void query_free(query_t *query)
free(query->owner);
free(query->port);
free(query->subnet);
free(query);
}
......@@ -1292,6 +1370,7 @@ static void dig_help(void)
" +[no]nsid Request NSID.\n"
" +[no]edns=N Use EDNS (=version).\n"
" +noidn Disable IDN transformation.\n"
" +client=SUBN Set EDNS client subnet IP/prefix.\n"
" +time=T Set wait for reply interval in seconds.\n"
" +retry=N Set number of retries.\n"
" +bufsize=B Set EDNS buffer size.\n"
......
......@@ -28,6 +28,7 @@
#include <stdbool.h> // bool
#include "libknot/libknot.h" // knot_addr_family_t
#include "utils/common/params.h" // protocol_t
#include "utils/common/exec.h" // sign_context_t
......@@ -70,6 +71,18 @@ typedef struct {
bool do_flag;
} flags_t;
/*! \brief Network subnet information. */
typedef struct {
/*! Protocol family. */
knot_addr_family_t family;
/*! Address in wire format. */
uint8_t addr[16];
/*! Length of address in wire format. */
uint16_t addr_len;
/*! Network mask length. */
uint8_t netmask;
} subnet_t;
/*! \brief Basic parameters for DNS query. */
typedef struct query query_t; // Forward declaration due to configuration.
struct query {
......@@ -123,6 +136,8 @@ struct query {
knot_key_params_t key_params;
/*!< Context for operations with signatures. */
sign_context_t sign_ctx;
/*!< EDNS client subnet. */
subnet_t *subnet;
#if USE_DNSTAP
/*!< Context for dnstap reader input. */
dt_reader_t *dt_reader;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment