From dad31179d218056a20091033fd19afb8e3e4dd03 Mon Sep 17 00:00:00 2001 From: Nick Peng Date: Sat, 3 Dec 2022 14:28:45 +0800 Subject: [PATCH] DNS: support parser TYPE65 RR. --- src/dns.c | 403 ++++++++++++++++++++++++++++++++++++++++++++++++++++- src/dns.h | 62 ++++++++- src/util.c | 56 ++++++++ 3 files changed, 516 insertions(+), 5 deletions(-) diff --git a/src/dns.c b/src/dns.c index 0d712f3..0ef3537 100644 --- a/src/dns.c +++ b/src/dns.c @@ -542,6 +542,106 @@ static int _dns_get_rr_head(struct dns_context *context, char *domain, int maxsi return len; } +struct dns_rr_nested *dns_add_rr_nested_start(struct dns_rr_nested *rr_nested_buffer, struct dns_packet *packet, + dns_rr_type type, dns_type_t rtype, const char *domain, int ttl) +{ + int len = 0; + memset(rr_nested_buffer, 0, sizeof(*rr_nested_buffer)); + rr_nested_buffer->type = type; + int ret = 0; + + /* resource record */ + /* |domain | + * |qtype | qclass | + * | ttl | + * | rrlen | rrdata | + */ + ret = _dns_add_rrs_start(packet, &rr_nested_buffer->context); + if (ret < 0) { + return NULL; + } + rr_nested_buffer->rr_start = rr_nested_buffer->context.ptr; + + /* add rr head */ + len = _dns_add_rr_head(&rr_nested_buffer->context, domain, rtype, DNS_C_IN, ttl, 0); + if (len < 0) { + return NULL; + } + rr_nested_buffer->rr_len_ptr = rr_nested_buffer->context.ptr - 2; + rr_nested_buffer->rr_head_len = len; + + return rr_nested_buffer; +} + +int dns_add_rr_nested_memcpy(struct dns_rr_nested *rr_nested, void *data, int data_len) +{ + if (rr_nested == NULL || data == NULL || data_len <= 0) { + return -1; + } + + if (_dns_left_len(&rr_nested->context) < data_len) { + return -1; + } + + memcpy(rr_nested->context.ptr, data, data_len); + rr_nested->context.ptr += data_len; + + return 0; +} + +int dns_add_rr_nested_end(struct dns_rr_nested *rr_nested, dns_type_t rtype) +{ + if (rr_nested == NULL || rr_nested->rr_start == NULL) { + return -1; + } + + int len = rr_nested->context.ptr - rr_nested->rr_start; + unsigned char *ptr = rr_nested->rr_len_ptr; + if (ptr == NULL || _dns_left_len(&rr_nested->context) < 2) { + return -1; + } + + _dns_write_short(&ptr, len - rr_nested->rr_head_len); + + return _dns_rr_add_end(rr_nested->context.packet, rr_nested->type, rtype, len); +} + +void *dns_get_rr_nested_start(struct dns_rrs *rrs, char *domain, int maxsize, int *qtype, int *ttl, int *rr_len) +{ + struct dns_context data_context; + int qclass = 0; + int ret = 0; + + _dns_init_context_by_rrs(rrs, &data_context); + ret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, qtype, &qclass, ttl, rr_len); + if (ret < 0) { + return NULL; + } + + if (qclass != DNS_C_IN) { + return NULL; + } + + if (*rr_len < 2) { + return NULL; + } + + return data_context.ptr; +} + +void *dns_get_rr_nested_next(struct dns_rrs *rrs, void *rr_nested, int rr_nested_len) +{ + void *end = rrs->data + rrs->len; + void *p = rr_nested + rr_nested_len; + if (p == end) { + return NULL; + } else if (p > end) { + return NULL; + } + + return p; +} + static int _dns_add_RAW(struct dns_packet *packet, dns_rr_type rrtype, dns_type_t rtype, const char *domain, int ttl, const void *raw, int raw_len) { @@ -966,6 +1066,155 @@ int dns_get_OPT_TCP_KEEYALIVE(struct dns_rrs *rrs, unsigned short *opt_code, uns return 0; } +int dns_add_HTTPS_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, dns_rr_type type, + const char *domain, int ttl, int priority, const char *target) +{ + svcparam_buffer = dns_add_rr_nested_start(svcparam_buffer, packet, type, DNS_T_HTTPS, domain, ttl); + if (svcparam_buffer == NULL) { + return -1; + } + + int target_len = strnlen(target, DNS_MAX_CNAME_LEN) + 1; + if (_dns_left_len(&svcparam_buffer->context) < 2 + target_len) { + return -1; + } + + /* add rr data */ + _dns_write_short(&svcparam_buffer->context.ptr, priority); + safe_strncpy((char *)svcparam_buffer->context.ptr, target, target_len); + svcparam_buffer->context.ptr += target_len; + + return 0; +} + +int dns_HTTPS_add_raw(struct dns_rr_nested *svcparam, unsigned short key, unsigned char *value, unsigned short len) +{ + if (_dns_left_len(&svcparam->context) < 2 + len) { + return -1; + } + + dns_add_rr_nested_memcpy(svcparam, &key, 2); + dns_add_rr_nested_memcpy(svcparam, &len, 2); + dns_add_rr_nested_memcpy(svcparam, value, len); + return 0; +} + +int dns_HTTPS_add_port(struct dns_rr_nested *svcparam, unsigned short port) +{ + if (_dns_left_len(&svcparam->context) < 6) { + return -1; + } + + unsigned short value = DNS_HTTPS_T_PORT; + dns_add_rr_nested_memcpy(svcparam, &value, 2); + value = 2; + dns_add_rr_nested_memcpy(svcparam, &value, 2); + value = htons(port); + dns_add_rr_nested_memcpy(svcparam, &value, 2); + + return 0; +} + +int dns_HTTPS_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len) +{ + if (_dns_left_len(&svcparam->context) < 2 + 2 + ech_len) { + return -1; + } + + unsigned short value = DNS_HTTPS_T_ECH; + dns_add_rr_nested_memcpy(svcparam, &value, 2); + + value = ech_len; + dns_add_rr_nested_memcpy(svcparam, &value, 2); + dns_add_rr_nested_memcpy(svcparam, ech, ech_len); + + return 0; +} + +int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char addr[][DNS_RR_A_LEN], int addr_num) +{ + if (_dns_left_len(&svcparam->context) < 4 + addr_num * DNS_RR_A_LEN) { + return -1; + } + + unsigned short value = DNS_HTTPS_T_IPV4HINT; + dns_add_rr_nested_memcpy(svcparam, &value, 2); + + value = addr_num * DNS_RR_A_LEN; + dns_add_rr_nested_memcpy(svcparam, &value, 2); + + for (int i = 0; i < addr_num; i++) { + dns_add_rr_nested_memcpy(svcparam, addr[i], DNS_RR_A_LEN); + } + + return 0; +} +int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char addr[][DNS_RR_AAAA_LEN], int addr_num) +{ + if (_dns_left_len(&svcparam->context) < 4 + addr_num * DNS_RR_AAAA_LEN) { + return -1; + } + + unsigned short value = DNS_HTTPS_T_IPV6HINT; + dns_add_rr_nested_memcpy(svcparam, &value, 2); + + value = addr_num * DNS_RR_AAAA_LEN; + dns_add_rr_nested_memcpy(svcparam, &value, 2); + + for (int i = 0; i < addr_num; i++) { + dns_add_rr_nested_memcpy(svcparam, addr[i], DNS_RR_AAAA_LEN); + } + + return 0; +} + +int dns_add_HTTPS_end(struct dns_rr_nested *svcparam) +{ + return dns_add_rr_nested_end(svcparam, DNS_T_HTTPS); +} + +struct dns_https_param *dns_get_HTTPS_svcparm_start(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, + int *priority, char *target, int target_size) +{ + int qtype = 0; + unsigned char *data = NULL; + int rr_len = 0; + + data = dns_get_rr_nested_start(rrs, domain, maxsize, &qtype, ttl, &rr_len); + if (data == NULL) { + return NULL; + } + + if (qtype != DNS_T_HTTPS) { + return NULL; + } + + if (rr_len < 2) { + return NULL; + } + + *priority = _dns_read_short(&data); + rr_len -= 2; + if (rr_len <= 0) { + return NULL; + } + + int len = strnlen((char *)data, rr_len); + safe_strncpy(target, (char *)data, target_size); + data += len + 1; + rr_len -= len + 1; + if (rr_len <= 0) { + return NULL; + } + + return (struct dns_https_param *)data; +} + +struct dns_https_param *dns_get_HTTPS_svcparm_next(struct dns_rrs *rrs, struct dns_https_param *param) +{ + return dns_get_rr_nested_next(rrs, param, sizeof(struct dns_https_param) + param->len); +} + /* * Format: * |DNS_NAME\0(string)|qtype(short)|qclass(short)| @@ -1624,7 +1873,7 @@ static int _dns_decode_opt(struct dns_context *context, dns_rr_type type, unsign opt_len = _dns_read_short(&context->ptr); if (_dns_left_len(context) < opt_len) { - tlog(TLOG_ERROR, "read opt data failed, opt_code = %d, opt_le = %d", opt_code, opt_len); + tlog(TLOG_ERROR, "read opt data failed, opt_code = %d, opt_len = %d", opt_code, opt_len); return -1; } @@ -1665,6 +1914,139 @@ static int _dns_decode_opt(struct dns_context *context, dns_rr_type type, unsign return 0; } +static int _dns_encode_HTTPS(struct dns_context *context, struct dns_rrs *rrs) +{ + int ret = 0; + int qtype = 0; + int qclass = 0; + char domain[DNS_MAX_CNAME_LEN]; + char target[DNS_MAX_CNAME_LEN] = {0}; + unsigned char *rr_len_ptr = NULL; + unsigned char *start = NULL; + unsigned char *rr_start = NULL; + int ttl = 0; + int priority = 0; + struct dns_https_param *param = NULL; + + param = dns_get_HTTPS_svcparm_start(rrs, domain, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN); + if (param == NULL) { + tlog(TLOG_ERROR, "get https param failed."); + return -1; + } + + ret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, 0, &rr_len_ptr); + if (ret < 0) { + return -1; + } + + rr_start = context->ptr; + if (_dns_left_len(context) < 2) { + tlog(TLOG_ERROR, "left len is invalid."); + return -1; + } + + _dns_write_short(&context->ptr, priority); + ret = _dns_encode_domain(context, target); + if (ret < 0) { + return -1; + } + + start = context->ptr; + for (; param != NULL; param = dns_get_HTTPS_svcparm_next(rrs, param)) { + if (context->ptr - start > rrs->len || _dns_left_len(context) <= 0) { + return -1; + } + + _dns_write_short(&context->ptr, param->key); + _dns_write_short(&context->ptr, param->len); + switch (param->key) { + case DNS_HTTPS_T_MANDATORY: + case DNS_HTTPS_T_NO_DEFAULT_ALPN: + case DNS_HTTPS_T_ALPN: + case DNS_HTTPS_T_PORT: + case DNS_HTTPS_T_IPV4HINT: + case DNS_HTTPS_T_ECH: + case DNS_HTTPS_T_IPV6HINT: { + memcpy(context->ptr, param->value, param->len); + context->ptr += param->len; + } break; + default: + /* skip unknown key */ + context->ptr -= 4; + break; + } + } + + _dns_write_short(&rr_len_ptr, context->ptr - rr_start); + + return 0; +} + +static int _dns_decode_HTTPS(struct dns_context *context, const char *domain, dns_rr_type type, unsigned int ttl, + int rr_len) +{ + unsigned char *start = context->ptr; + + struct dns_packet *packet = context->packet; + int ret = 0; + unsigned short priority; + unsigned short key; + unsigned short value_len; + unsigned char *value = NULL; + char target[DNS_MAX_CNAME_LEN] = {0}; + struct dns_rr_nested param; + + if (rr_len < 2) { + tlog(TLOG_DEBUG, "https len is invalid."); + return -1; + } + + priority = _dns_read_short(&context->ptr); + ret = _dns_decode_domain(context, target, sizeof(target)); + if (ret < 0) { + return -1; + } + + dns_add_HTTPS_start(¶m, packet, DNS_RRS_AN, domain, ttl, priority, target); + + while (context->ptr - start < rr_len) { + if (_dns_left_len(context) < 4) { + tlog(TLOG_WARN, "data length is invalid, %d:%d", _dns_left_len(context), + (int)(context->ptr - context->data)); + return -1; + } + key = _dns_read_short(&context->ptr); + value_len = _dns_read_short(&context->ptr); + value = context->ptr; + + if (_dns_left_len(context) < value_len) { + tlog(TLOG_ERROR, "read https data failed, svcParam key = %d, https_len = %d", key, value_len); + return -1; + } + + switch (key) { + case DNS_HTTPS_T_MANDATORY: + case DNS_HTTPS_T_ALPN: + case DNS_HTTPS_T_NO_DEFAULT_ALPN: + case DNS_HTTPS_T_PORT: + case DNS_HTTPS_T_IPV4HINT: + case DNS_HTTPS_T_ECH: + case DNS_HTTPS_T_IPV6HINT: { + dns_HTTPS_add_raw(¶m, key, value, value_len); + } break; + default: + tlog(TLOG_DEBUG, "DNS HTTPS key = %d not supported", key); + break; + } + + context->ptr += value_len; + } + + dns_add_HTTPS_end(¶m); + + return 0; +} + static int _dns_decode_qd(struct dns_context *context) { struct dns_packet *packet = context->packet; @@ -1808,6 +2190,19 @@ static int _dns_decode_an(struct dns_context *context, dns_rr_type type) dns_set_OPT_payload_size(packet, qclass); } break; + case DNS_T_HTTPS: { + unsigned char *https_start = context->ptr; + ret = _dns_decode_HTTPS(context, domain, type, ttl, rr_len); + if (ret < 0) { + tlog(TLOG_DEBUG, "decode HTTPS failed, %s", domain); + return -1; + } + + if (context->ptr - https_start != rr_len) { + tlog(TLOG_DEBUG, "opt length mismatch, %s\n", domain); + return -1; + } + } break; default: { unsigned char raw_data[1024]; if (_dns_left_len(context) < rr_len || rr_len >= (int)sizeof(raw_data)) { @@ -1886,6 +2281,12 @@ static int _dns_encode_an(struct dns_context *context, struct dns_rrs *rrs) return -1; } break; + case DNS_T_HTTPS: + ret = _dns_encode_HTTPS(context, rrs); + if (ret < 0) { + return -1; + } + break; default: ret = _dns_encode_raw(context, rrs); if (ret < 0) { diff --git a/src/dns.h b/src/dns.h index 59938f0..027a0df 100644 --- a/src/dns.h +++ b/src/dns.h @@ -73,13 +73,25 @@ typedef enum dns_type { } dns_type_t; typedef enum dns_opt_code { - DNS_OPT_T_ECS = 8, // OPT ECS - DNS_OPT_T_COOKIE = 10, //OPT Cookie + DNS_OPT_T_ECS = 8, // OPT ECS + DNS_OPT_T_COOKIE = 10, // OPT Cookie DNS_OPT_T_TCP_KEEPALIVE = 11, DNS_OPT_T_PADDING = 12, DNS_OPT_T_ALL = 255 } dns_opt_code_t; +/* https://datatracker.ietf.org/doc/draft-ietf-dnsop-svcb-https/11/ */ +typedef enum dns_htts_svcparam { + DNS_HTTPS_T_MANDATORY = 0, + DNS_HTTPS_T_ALPN = 1, + DNS_HTTPS_T_NO_DEFAULT_ALPN = 2, + DNS_HTTPS_T_PORT = 3, + DNS_HTTPS_T_IPV4HINT = 4, + DNS_HTTPS_T_ECH = 5, + DNS_HTTPS_T_IPV6HINT = 6, + DNS_HTTPS_T_ALL = 255 +} dns_htts_svcparam_t; + typedef enum dns_opcode { DNS_OP_QUERY = 0, DNS_OP_IQUERY = 1, @@ -184,7 +196,7 @@ struct dns_opt_ecs { unsigned char source_prefix; unsigned char scope_prefix; unsigned char addr[DNS_RR_AAAA_LEN]; -} __attribute__((packed));; +} __attribute__((packed)); /* OPT COOLIE */ struct dns_opt_cookie { @@ -200,9 +212,31 @@ struct dns_opt { unsigned char data[0]; } __attribute__((packed)); +struct dns_rr_nested { + struct dns_context context; + unsigned char *rr_start; + unsigned char *rr_len_ptr; + unsigned short rr_head_len; + dns_rr_type type; +}; + +struct dns_https_param { + unsigned short key; + unsigned short len; + unsigned char value[0]; +}; + struct dns_rrs *dns_get_rrs_next(struct dns_packet *packet, struct dns_rrs *rrs); struct dns_rrs *dns_get_rrs_start(struct dns_packet *packet, dns_rr_type type, int *count); +struct dns_rr_nested *dns_add_rr_nested_start(struct dns_rr_nested *rr_nested_buffer, struct dns_packet *packet, + dns_rr_type type, dns_type_t rtype, const char *domain, int ttl); +int dns_add_rr_nested_end(struct dns_rr_nested *rr_nested, dns_type_t rtype); +int dns_add_rr_nested_memcpy(struct dns_rr_nested *rr_nested, void *data, int data_len); + +void *dns_get_rr_nested_start(struct dns_rrs *rrs, char *domain, int maxsize, int *qtype, int *ttl, int *rr_len); +void *dns_get_rr_nested_next(struct dns_rrs *rrs, void *rr_nested, int rr_nested_len); + /* * Question */ @@ -215,7 +249,8 @@ int dns_get_domain(struct dns_rrs *rrs, char *domain, int maxsize, int *qtype, i int dns_add_CNAME(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, const char *cname); int dns_get_CNAME(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, char *cname, int cname_size); -int dns_add_A(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, unsigned char addr[DNS_RR_A_LEN]); +int dns_add_A(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, + unsigned char addr[DNS_RR_A_LEN]); int dns_get_A(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, unsigned char addr[DNS_RR_A_LEN]); int dns_add_PTR(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, char *cname); @@ -240,6 +275,25 @@ int dns_get_OPT_ECS(struct dns_rrs *rrs, unsigned short *opt_code, unsigned shor int dns_add_OPT_TCP_KEEYALIVE(struct dns_packet *packet, unsigned short timeout); int dns_get_OPT_TCP_KEEYALIVE(struct dns_rrs *rrs, unsigned short *opt_code, unsigned short *opt_len, unsigned short *timeout); + +int dns_add_HTTPS_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, + dns_rr_type type, const char *domain, int ttl, int priority, + const char *target); +int dns_HTTPS_add_raw(struct dns_rr_nested *svcparam, unsigned short key, unsigned char *value, unsigned short len); +int dns_HTTPS_add_port(struct dns_rr_nested *svcparam, unsigned short port); +int dns_HTTPS_add_alpn(struct dns_rr_nested *svcparam, const char *alpn); +int dns_HTTPS_add_no_default_alpn(struct dns_rr_nested *svcparam); +int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char addr[][DNS_RR_A_LEN], + int addr_num); +int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char addr[][DNS_RR_AAAA_LEN], + int addr_num); +int dns_HTTPS_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len); +int dns_add_HTTPS_end(struct dns_rr_nested *svcparam); + +struct dns_https_param *dns_get_HTTPS_svcparm_start(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, + int *priority, char *target, int target_size); +struct dns_https_param *dns_get_HTTPS_svcparm_next(struct dns_rrs *rrs, struct dns_https_param *parm); + /* * Packet operation */ diff --git a/src/util.c b/src/util.c index 938d1a9..e2dc4be 100644 --- a/src/util.c +++ b/src/util.c @@ -1373,6 +1373,62 @@ static int _dns_debug_display(struct dns_packet *packet) inet_ntop(AF_INET6, addr, req_host, sizeof(req_host)); printf("domain: %s AAAA: %s TTL:%d\n", name, req_host, ttl); } break; + case DNS_T_HTTPS: { + char name[DNS_MAX_CNAME_LEN] = {0}; + char target[DNS_MAX_CNAME_LEN] = {0}; + struct dns_https_param *p = NULL; + int priority = 0; + + p = dns_get_HTTPS_svcparm_start(rrs, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target, + DNS_MAX_CNAME_LEN); + if (p == NULL) { + printf("get HTTPS svcparm failed\n"); + break; + } + + printf("domain: %s HTTPS: %s TTL: %d priority: %d\n", name, target, ttl, priority); + + for (; p; p = dns_get_HTTPS_svcparm_next(rrs, p)) { + switch (p->key) { + case DNS_HTTPS_T_MANDATORY: { + printf(" HTTPS: mandatory: %s\n", p->value); + } break; + case DNS_HTTPS_T_ALPN: { + printf(" HTTPS: alpn: %s\n", p->value); + } break; + case DNS_HTTPS_T_NO_DEFAULT_ALPN: { + printf(" HTTPS: no_default_alpn: %s\n", p->value); + } break; + case DNS_HTTPS_T_PORT: { + int port = *(unsigned short *)(p->value); + printf(" HTTPS: port: %d\n", port); + } break; + case DNS_HTTPS_T_IPV4HINT: { + printf(" HTTPS: ipv4hint: %d\n", p->len / 4); + for (int k = 0; k < p->len / 4; k++) { + char ip[16] = {0}; + inet_ntop(AF_INET, p->value + k * 4, ip, sizeof(ip)); + printf(" ipv4: %s\n", ip); + } + } break; + case DNS_HTTPS_T_ECH: { + printf(" HTTPS: ech: "); + for (int k = 0; k < p->len; k++) { + printf("%02x ", p->value[k]); + } + printf("\n"); + } break; + case DNS_HTTPS_T_IPV6HINT: { + printf(" HTTPS: ipv6hint: %d\n", p->len / 16); + for (int k = 0; k < p->len / 16; k++) { + char ip[64] = {0}; + inet_ntop(AF_INET6, p->value + k * 16, ip, sizeof(ip)); + printf(" ipv6: %s\n", ip); + } + } break; + } + } + } break; case DNS_T_NS: { char cname[DNS_MAX_CNAME_LEN]; char name[DNS_MAX_CNAME_LEN] = {0};