/************************************************************************* * * Copyright (C) 2018-2020 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * smartdns is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define _GNU_SOURCE #include "dns.h" #include "stringutil.h" #include "tlog.h" #include #include #include #include #include #include #include #define QR_MASK 0x8000 #define OPCODE_MASK 0x7800 #define AA_MASK 0x0400 #define TC_MASK 0x0200 #define RD_MASK 0x0100 #define RA_MASK 0x0080 #define RCODE_MASK 0x000F #define DNS_RR_END (0XFFFF) #define UNUSED(expr) \ do { \ (void)(expr); \ } while (0) /* read short and move pointer */ static unsigned short _dns_read_short(unsigned char **buffer) { unsigned short value = 0; value = ntohs(*((unsigned short *)(*buffer))); *buffer += 2; return value; } /* write char and move pointer */ static __attribute__((unused)) void _dns_write_char(unsigned char **buffer, unsigned char value) { **buffer = value; *buffer += 1; } /* read char and move pointer */ static unsigned char _dns_read_char(unsigned char **buffer) { unsigned char value = **buffer; *buffer += 1; return value; } /* write short and move pointer */ static void _dns_write_short(unsigned char **buffer, unsigned short value) { value = htons(value); *((unsigned short *)(*buffer)) = value; *buffer += 2; } /* write int and move pointer */ static void _dns_write_int(unsigned char **buffer, unsigned int value) { value = htonl(value); *((unsigned int *)(*buffer)) = value; *buffer += 4; } /* read int and move pointer */ static unsigned int _dns_read_int(unsigned char **buffer) { unsigned int value = 0; value = ntohl(*((unsigned int *)(*buffer))); *buffer += 4; return value; } static inline int _dns_left_len(struct dns_context *context) { return context->maxsize - (context->ptr - context->data); } static int _dns_get_domain_from_packet(unsigned char *packet, int packet_size, unsigned char **domain_ptr, char *output, int size) { int output_len = 0; int copy_len = 0; int len = 0; unsigned char *ptr = *domain_ptr; int is_compressed = 0; int ptr_jump = 0; /*[len]string[len]string...[0]0 */ while (1) { if (ptr >= packet + packet_size || ptr < packet || output_len >= size - 1 || ptr_jump > 4) { return -1; } len = *ptr; if (len == 0) { *output = 0; ptr++; break; } /* compressed domain */ if (len >= 0xC0) { if ((ptr + 2) > (packet + packet_size)) { return -1; } /* 0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | 1 1| OFFSET | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ */ /* read offset */ len = _dns_read_short(&ptr) & 0x3FFF; if (is_compressed == 0) { *domain_ptr = ptr; } ptr = packet + len; if (ptr > packet + packet_size) { tlog(TLOG_DEBUG, "length is not enough %u:%ld, %p, %p", packet_size, (long)(ptr - packet), *domain_ptr, packet); return -1; } is_compressed = 1; ptr_jump++; continue; } ptr_jump = 0; /* change [len] to '.' */ if (output_len > 0) { *output = '.'; output++; output_len += 1; } if (ptr > packet + packet_size) { tlog(TLOG_DEBUG, "length is not enough %u:%ld, %p, %p", packet_size, (long)(ptr - packet), *domain_ptr, packet); return -1; } ptr++; if (output_len < size - 1) { /* copy sub string */ copy_len = (len < size - output_len) ? len : size - 1 - output_len; if ((ptr + copy_len) > (packet + packet_size)) { tlog(TLOG_DEBUG, "length is not enough %u:%ld, %p, %p", packet_size, (long)(ptr - packet), *domain_ptr, packet); return -1; } memcpy(output, ptr, copy_len); } ptr += len; output += len; output_len += len; } if (is_compressed == 0) { *domain_ptr = ptr; } return 0; } static int _dns_decode_domain(struct dns_context *context, char *output, int size) { return _dns_get_domain_from_packet(context->data, context->maxsize, &(context->ptr), output, size); } static unsigned int dict_hash(const char *s) { unsigned int hashval = 0; for (hashval = 0; *s != '\0'; s++) { hashval = *s + 31 * hashval; } return hashval; } static int _dns_add_domain_dict(struct dns_context *context, unsigned int hash, int pos) { struct dns_packet_dict *dict = context->namedict; if (dict->dict_count >= DNS_PACKET_DICT_SIZE) { return -1; } if (hash == 0) { return -1; } if (pos >= context->maxsize) { return -1; } int index = dict->dict_count; dict->names[index].hash = hash; dict->names[index].pos = pos; dict->dict_count++; return 0; } static int _dns_get_domain_offset(struct dns_context *context, const char *domain) { int i = 0; char domain_check[DNS_MAX_CNAME_LEN]; struct dns_packet_dict *dict = context->namedict; if (*domain == '\0') { return -1; } unsigned int hash = dict_hash(domain); for (i = 0; i < dict->dict_count; i++) { if (dict->names[i].hash != hash) { continue; } unsigned char *domain_check_ptr = dict->names[i].pos + context->data; if (_dns_get_domain_from_packet(context->data, context->maxsize, &domain_check_ptr, domain_check, DNS_MAX_CNAME_LEN) != 0) { return -1; } return dict->names[i].pos; } _dns_add_domain_dict(context, hash, context->ptr - 1 - context->data); return -1; } static int _dns_encode_domain(struct dns_context *context, const char *domain) { int num = 0; int total_len = 0; unsigned char *ptr_num = context->ptr++; int dict_offset = 0; dict_offset = _dns_get_domain_offset(context, domain); total_len++; /*[len]string[len]string...[0]0 */ while (_dns_left_len(context) > 1 && *domain != 0) { total_len++; if (dict_offset >= 0) { int offset = 0xc000 | dict_offset; _dns_write_short(&ptr_num, offset); context->ptr++; ptr_num = NULL; return total_len; } if (*domain == '.') { *ptr_num = num; num = 0; ptr_num = context->ptr; domain++; context->ptr++; dict_offset = _dns_get_domain_offset(context, domain); continue; } *context->ptr = *domain; num++; context->ptr++; domain++; } *ptr_num = num; if (total_len > 1) { /* if domain is '\0', [domain] is '\0' */ *(context->ptr) = 0; total_len++; context->ptr++; } if (_dns_left_len(context) <= 0) { return -1; } return total_len; } /* iterator get rrs begin */ struct dns_rrs *dns_get_rrs_start(struct dns_packet *packet, dns_rr_type type, int *count) { unsigned short start = 0; struct dns_head *head = &packet->head; /* get rrs count by rrs type */ switch (type) { case DNS_RRS_QD: *count = head->qdcount; start = packet->questions; break; case DNS_RRS_AN: *count = head->ancount; start = packet->answers; break; case DNS_RRS_NS: *count = head->nscount; start = packet->nameservers; break; case DNS_RRS_NR: *count = head->nrcount; start = packet->additional; break; case DNS_RRS_OPT: *count = packet->optcount; start = packet->optional; break; default: return NULL; break; } /* if not resource record, return null */ if (start == DNS_RR_END) { return NULL; } /* return rrs data start address */ return (struct dns_rrs *)(packet->data + start); } /* iterator next rrs */ struct dns_rrs *dns_get_rrs_next(struct dns_packet *packet, struct dns_rrs *rrs) { if (rrs->next == DNS_RR_END) { return NULL; } return (struct dns_rrs *)(packet->data + rrs->next); } static void _dns_init_context_by_rrs(struct dns_rrs *rrs, struct dns_context *context) { context->packet = rrs->packet; context->data = rrs->packet->data; context->ptr = rrs->data; context->namedict = &rrs->packet->namedict; context->maxsize = rrs->data - rrs->packet->data + rrs->len; } /* iterator add rrs begin */ static int _dns_add_rrs_start(struct dns_packet *packet, struct dns_context *context) { struct dns_rrs *rrs = NULL; unsigned char *end = packet->data + packet->len; if ((packet->len + (int)sizeof(*rrs)) >= packet->size) { return -1; } rrs = (struct dns_rrs *)end; context->ptr = rrs->data; context->packet = packet; context->maxsize = packet->size - sizeof(*packet); context->data = packet->data; context->namedict = &packet->namedict; return 0; } /* iterator add rrs end */ static int _dns_rr_add_end(struct dns_packet *packet, int type, dns_type_t rtype, int len) { struct dns_rrs *rrs = NULL; struct dns_rrs *rrs_next = NULL; struct dns_head *head = &packet->head; unsigned char *end = packet->data + packet->len; unsigned short *count = NULL; unsigned short *start = NULL; rrs = (struct dns_rrs *)end; if (packet->len + len > packet->size - (int)sizeof(*packet) - (int)sizeof(*rrs)) { return -1; } switch (type) { case DNS_RRS_QD: count = &head->qdcount; start = &packet->questions; break; case DNS_RRS_AN: count = &head->ancount; start = &packet->answers; break; case DNS_RRS_NS: count = &head->nscount; start = &packet->nameservers; break; case DNS_RRS_NR: count = &head->nrcount; start = &packet->additional; break; case DNS_RRS_OPT: count = &packet->optcount; start = &packet->optional; break; default: return -1; break; } /* add data to end of dns_packet, and set previous rrs point to this rrs */ if (*start != DNS_RR_END) { rrs_next = (struct dns_rrs *)(packet->data + *start); while (rrs_next->next != DNS_RR_END) { rrs_next = (struct dns_rrs *)(packet->data + rrs_next->next); } rrs_next->next = packet->len; } else { *start = packet->len; } /* update rrs head info */ rrs->packet = packet; rrs->len = len; rrs->type = rtype; rrs->next = DNS_RR_END; /* update total data length */ *count += 1; packet->len += len + sizeof(*rrs); return 0; } static int _dns_add_qr_head(struct dns_context *context, const char *domain, int qtype, int qclass) { int ret = _dns_encode_domain(context, domain); if (ret < 0) { return -1; } if (_dns_left_len(context) < 4) { return -1; } _dns_write_short(&context->ptr, qtype); _dns_write_short(&context->ptr, qclass); return ret + 4; } static int _dns_get_qr_head(struct dns_context *context, char *domain, int maxsize, int *qtype, int *qclass) { int ret = 0; if (domain == NULL || context == NULL) { return -1; } ret = _dns_decode_domain(context, domain, maxsize); if (ret < 0) { return -1; } if (_dns_left_len(context) < 4) { return -1; } *qtype = _dns_read_short(&context->ptr); *qclass = _dns_read_short(&context->ptr); return 0; } static int _dns_add_rr_head(struct dns_context *context, const char *domain, int qtype, int qclass, int ttl, int rr_len) { int len = 0; /* resource record head */ /* |domain | * |qtype | qclass | * | ttl | * | rrlen | rrdata | */ len = _dns_add_qr_head(context, domain, qtype, qclass); if (len < 0) { return -1; } if (_dns_left_len(context) < 6) { return -1; } _dns_write_int(&context->ptr, ttl); _dns_write_short(&context->ptr, rr_len); return len + 6; } static int _dns_get_rr_head(struct dns_context *context, char *domain, int maxsize, int *qtype, int *qclass, int *ttl, int *rr_len) { int len = 0; /* resource record head */ /* |domain | * |qtype | qclass | * | ttl | * | rrlen | rrdata | */ len = _dns_get_qr_head(context, domain, maxsize, qtype, qclass); if (_dns_left_len(context) < 6) { return -1; } *ttl = _dns_read_int(&context->ptr); *rr_len = _dns_read_short(&context->ptr); return len; } 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) { int len = 0; struct dns_context context; int ret = 0; if (raw_len < 0) { return -1; } /* resource record */ /* |domain | * |qtype | qclass | * | ttl | * | rrlen | rrdata | */ ret = _dns_add_rrs_start(packet, &context); if (ret < 0) { return -1; } /* add rr head */ len = _dns_add_rr_head(&context, domain, rtype, DNS_C_IN, ttl, raw_len); if (len < 0) { return -1; } if (_dns_left_len(&context) < raw_len) { return -1; } /* add rr data */ memcpy(context.ptr, raw, raw_len); context.ptr += raw_len; len += raw_len; return _dns_rr_add_end(packet, rrtype, rtype, len); } static int _dns_get_RAW(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, void *raw, int *raw_len) { int qtype = 0; int qclass = 0; int rr_len = 0; int ret = 0; struct dns_context context; /* resource record head */ /* |domain | * |qtype | qclass | * | ttl | * | rrlen | rrdata | */ _dns_init_context_by_rrs(rrs, &context); /* get rr head */ ret = _dns_get_rr_head(&context, domain, maxsize, &qtype, &qclass, ttl, &rr_len); if (ret < 0) { return -1; } if (qtype != rrs->type || rr_len > *raw_len) { return -1; } /* get rr data */ memcpy(raw, context.ptr, rr_len); context.ptr += rr_len; *raw_len = rr_len; return 0; } static int _dns_add_opt_RAW(struct dns_packet *packet, dns_opt_code_t opt_rrtype, void *raw, int raw_len) { unsigned char opt_data[DNS_MAX_OPT_LEN]; struct dns_opt *opt = (struct dns_opt *)opt_data; int len = 0; opt->code = DNS_OPT_T_TCP_KEEPALIVE; opt->length = sizeof(unsigned short); memcpy(opt->data, raw, raw_len); len += raw_len; len += sizeof(*opt); return _dns_add_RAW(packet, DNS_RRS_OPT, (dns_type_t)DNS_OPT_T_TCP_KEEPALIVE, "", 0, opt_data, len); } static int _dns_get_opt_RAW(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, struct dns_opt *dns_opt, int *dns_optlen) { *dns_optlen = DNS_MAX_OPT_LEN; return _dns_get_RAW(rrs, domain, maxsize, ttl, dns_opt, dns_optlen); } static int __attribute__((unused)) _dns_add_OPT(struct dns_packet *packet, dns_rr_type type, unsigned short opt_code, unsigned short opt_len, struct dns_opt *opt) { // TODO int ret = 0; int len = 0; struct dns_context context; int total_len = sizeof(*opt) + opt->length; int ttl = 0; /* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0: | OPTION-CODE | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2: | OPTION-LENGTH | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4: | | / OPTION-DATA / / / +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ ret = _dns_add_rrs_start(packet, &context); if (ret < 0) { return -1; } if (_dns_left_len(&context) < total_len) { return -1; } ttl = (opt_code << 16) | opt_len; /* add rr head */ len = _dns_add_rr_head(&context, "", type, DNS_C_IN, ttl, total_len); if (len < 0) { return -1; } /* add rr data */ memcpy(context.ptr, opt, total_len); context.ptr += total_len; len = context.ptr - context.data - packet->len; return _dns_rr_add_end(packet, type, DNS_T_OPT, len); } static int __attribute__((unused)) _dns_get_OPT(struct dns_rrs *rrs, unsigned short *opt_code, unsigned short *opt_len, struct dns_opt *opt, int *opt_maxlen) { // TODO int qtype = 0; int qclass = 0; int rr_len = 0; int ret = 0; struct dns_context context; char domain[DNS_MAX_CNAME_LEN]; int maxsize = DNS_MAX_CNAME_LEN; int ttl = 0; _dns_init_context_by_rrs(rrs, &context); /* get rr head */ ret = _dns_get_rr_head(&context, domain, maxsize, &qtype, &qclass, &ttl, &rr_len); if (ret < 0) { return -1; } if (qtype != rrs->type || rr_len > *opt_len) { return -1; } /* get rr data */ *opt_code = ttl >> 16; *opt_len = ttl & 0xFFFF; memcpy(opt, context.ptr, rr_len); context.ptr += rr_len; *opt_maxlen = rr_len; return 0; } int dns_add_CNAME(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, const char *cname) { int rr_len = strnlen(cname, DNS_MAX_CNAME_LEN) + 1; return _dns_add_RAW(packet, type, DNS_T_CNAME, domain, ttl, cname, rr_len); } int dns_get_CNAME(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, char *cname, int cname_size) { int len = cname_size; return _dns_get_RAW(rrs, domain, maxsize, ttl, cname, &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]) { return _dns_add_RAW(packet, type, DNS_T_A, domain, ttl, 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 len = DNS_RR_A_LEN; return _dns_get_RAW(rrs, domain, maxsize, ttl, addr, &len); } int dns_add_PTR(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, char *cname) { int rr_len = strnlen(cname, DNS_MAX_CNAME_LEN) + 1; return _dns_add_RAW(packet, type, DNS_T_PTR, domain, ttl, cname, rr_len); } int dns_get_PTR(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, char *cname, int cname_size) { int len = cname_size; return _dns_get_RAW(rrs, domain, maxsize, ttl, cname, &len); } int dns_add_NS(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, const char *cname) { int rr_len = strnlen(cname, DNS_MAX_CNAME_LEN) + 1; return _dns_add_RAW(packet, type, DNS_T_NS, domain, ttl, cname, rr_len); } int dns_get_NS(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, char *cname, int cname_size) { int len = cname_size; return _dns_get_RAW(rrs, domain, maxsize, ttl, cname, &len); } int dns_add_AAAA(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, unsigned char addr[DNS_RR_AAAA_LEN]) { return _dns_add_RAW(packet, type, DNS_T_AAAA, domain, ttl, addr, DNS_RR_AAAA_LEN); } int dns_get_AAAA(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, unsigned char addr[DNS_RR_AAAA_LEN]) { int len = DNS_RR_AAAA_LEN; return _dns_get_RAW(rrs, domain, maxsize, ttl, addr, &len); } int dns_add_SOA(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, struct dns_soa *soa) { /* SOA */ /*| mname | *| rname | *| serial | *| refersh | *| retry | *| expire | *| minimum | */ unsigned char data[sizeof(*soa)]; unsigned char *ptr = data; int len = 0; safe_strncpy((char *)ptr, soa->mname, DNS_MAX_CNAME_LEN); ptr += strnlen(soa->mname, DNS_MAX_CNAME_LEN - 1) + 1; safe_strncpy((char *)ptr, soa->rname, DNS_MAX_CNAME_LEN); ptr += strnlen(soa->rname, DNS_MAX_CNAME_LEN - 1) + 1; memcpy(ptr, &soa->serial, sizeof(unsigned int)); ptr += 4; memcpy(ptr, &soa->refresh, sizeof(unsigned int)); ptr += 4; memcpy(ptr, &soa->retry, sizeof(unsigned int)); ptr += 4; memcpy(ptr, &soa->expire, sizeof(unsigned int)); ptr += 4; memcpy(ptr, &soa->minimum, sizeof(unsigned int)); ptr += 4; len = ptr - data; return _dns_add_RAW(packet, type, DNS_T_SOA, domain, ttl, data, len); } int dns_get_SOA(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, struct dns_soa *soa) { unsigned char data[sizeof(*soa)]; unsigned char *ptr = data; int len = sizeof(data); /* SOA */ /*| mname | *| rname | *| serial | *| refersh | *| retry | *| expire | *| minimum | */ if (_dns_get_RAW(rrs, domain, maxsize, ttl, data, &len) != 0) { return -1; } safe_strncpy(soa->mname, (char *)ptr, DNS_MAX_CNAME_LEN - 1); ptr += strnlen(soa->mname, DNS_MAX_CNAME_LEN - 1) + 1; if (ptr - data >= len) { return -1; } safe_strncpy(soa->rname, (char *)ptr, DNS_MAX_CNAME_LEN - 1); ptr += strnlen(soa->rname, DNS_MAX_CNAME_LEN - 1) + 1; if (ptr - data + 20 > len) { return -1; } memcpy(&soa->serial, ptr, 4); ptr += 4; memcpy(&soa->refresh, ptr, 4); ptr += 4; memcpy(&soa->retry, ptr, 4); ptr += 4; memcpy(&soa->expire, ptr, 4); ptr += 4; memcpy(&soa->minimum, ptr, 4); return 0; } int dns_set_OPT_payload_size(struct dns_packet *packet, int payload_size) { if (payload_size < 512) { payload_size = 512; } packet->payloadsize = payload_size; return 0; } int dns_get_OPT_payload_size(struct dns_packet *packet) { return packet->payloadsize; } int dns_add_OPT_ECS(struct dns_packet *packet, struct dns_opt_ecs *ecs) { unsigned char opt_data[DNS_MAX_OPT_LEN]; struct dns_opt *opt = (struct dns_opt *)opt_data; int len = 0; /* ecs size 4 + bit of address*/ len = 4; len += (ecs->source_prefix / 8); len += (ecs->source_prefix % 8 > 0) ? 1 : 0; opt->length = len; opt->code = DNS_OPT_T_ECS; memcpy(opt->data, ecs, len); len += sizeof(*opt); return _dns_add_RAW(packet, DNS_RRS_OPT, (dns_type_t)DNS_OPT_T_ECS, "", 0, opt_data, len); } int dns_get_OPT_ECS(struct dns_rrs *rrs, unsigned short *opt_code, unsigned short *opt_len, struct dns_opt_ecs *ecs) { unsigned char opt_data[DNS_MAX_OPT_LEN]; char domain[DNS_MAX_CNAME_LEN] = {0}; struct dns_opt *opt = (struct dns_opt *)opt_data; int len = DNS_MAX_OPT_LEN; int ttl = 0; if (_dns_get_RAW(rrs, domain, DNS_MAX_CNAME_LEN, &ttl, opt_data, &len) != 0) { return -1; } if (len < (int)sizeof(*opt)) { return -1; } if (opt->code != DNS_OPT_T_ECS) { return -1; } memcpy(ecs, opt->data, opt->length); return 0; } int dns_add_OPT_TCP_KEEYALIVE(struct dns_packet *packet, unsigned short timeout) { unsigned short timeout_net = htons(timeout); int data_len = 0; if (timeout > 0) { data_len = sizeof(timeout); } return _dns_add_opt_RAW(packet, DNS_OPT_T_TCP_KEEPALIVE, &timeout_net, data_len); } int dns_get_OPT_TCP_KEEYALIVE(struct dns_rrs *rrs, unsigned short *opt_code, unsigned short *opt_len, unsigned short *timeout) { unsigned char opt_data[DNS_MAX_OPT_LEN]; struct dns_opt *opt = (struct dns_opt *)opt_data; int len = DNS_MAX_OPT_LEN; int ttl = 0; unsigned char *data = NULL; if (_dns_get_opt_RAW(rrs, NULL, 0, &ttl, opt, &len) != 0) { return -1; } if (len < (int)sizeof(*opt)) { return -1; } if (opt->code != DNS_OPT_T_TCP_KEEPALIVE) { return -1; } if (opt->length == 0) { *timeout = 0; return 0; } if (opt->length != sizeof(unsigned short)) { return -1; } data = opt->data; *timeout = _dns_read_short(&data); return 0; } /* * Format: * |DNS_NAME\0(string)|qtype(short)|qclass(short)| */ int dns_add_domain(struct dns_packet *packet, const char *domain, int qtype, int qclass) { int len = 0; int ret = 0; struct dns_context context; ret = _dns_add_rrs_start(packet, &context); if (ret < 0) { return -1; } len = _dns_add_qr_head(&context, domain, qtype, qclass); if (len < 0) { return -1; } return _dns_rr_add_end(packet, DNS_RRS_QD, DNS_T_CNAME, len); } int dns_get_domain(struct dns_rrs *rrs, char *domain, int maxsize, int *qtype, int *qclass) { struct dns_context context; if (rrs->type != DNS_T_CNAME) { return -1; } _dns_init_context_by_rrs(rrs, &context); return _dns_get_qr_head(&context, domain, maxsize, qtype, qclass); } static int _dns_decode_head(struct dns_context *context) { unsigned int fields = 0; int len = 12; struct dns_head *head = &context->packet->head; if (_dns_left_len(context) < len) { return -1; } /* 0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ */ head->id = _dns_read_short(&context->ptr); fields = _dns_read_short(&context->ptr); head->qr = (fields & QR_MASK) >> 15; head->opcode = (fields & OPCODE_MASK) >> 11; head->aa = (fields & AA_MASK) >> 10; head->tc = (fields & TC_MASK) >> 9; head->rd = (fields & RD_MASK) >> 8; head->ra = (fields & RA_MASK) >> 7; head->rcode = (fields & RCODE_MASK) >> 0; head->qdcount = _dns_read_short(&context->ptr); head->ancount = _dns_read_short(&context->ptr); head->nscount = _dns_read_short(&context->ptr); head->nrcount = _dns_read_short(&context->ptr); return 0; } static int _dns_encode_head(struct dns_context *context) { int len = 12; struct dns_head *head = &context->packet->head; if (_dns_left_len(context) < len) { return -1; } _dns_write_short(&context->ptr, head->id); int fields = 0; fields |= (head->qr << 15) & QR_MASK; fields |= (head->opcode << 11) & OPCODE_MASK; fields |= (head->aa << 10) & AA_MASK; fields |= (head->tc << 9) & TC_MASK; fields |= (head->rd << 8) & RD_MASK; fields |= (head->ra << 7) & RA_MASK; fields |= (head->rcode << 0) & RCODE_MASK; _dns_write_short(&context->ptr, fields); _dns_write_short(&context->ptr, head->qdcount); _dns_write_short(&context->ptr, head->ancount); _dns_write_short(&context->ptr, head->nscount); _dns_write_short(&context->ptr, head->nrcount); return len; } static int _dns_encode_head_count(struct dns_context *context) { int len = 12; struct dns_head *head = &context->packet->head; unsigned char *ptr = context->data; ptr += 4; _dns_write_short(&ptr, head->qdcount); _dns_write_short(&ptr, head->ancount); _dns_write_short(&ptr, head->nscount); _dns_write_short(&ptr, head->nrcount); return len; } static int _dns_decode_qr_head(struct dns_context *context, char *domain, int domain_size, int *qtype, int *qclass) { int ret = 0; /* 0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / / / NAME / | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | CLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ */ ret = _dns_decode_domain(context, domain, domain_size); if (ret < 0) { tlog(TLOG_DEBUG, "decode domain failed."); return -1; } if (_dns_left_len(context) < 4) { tlog(TLOG_DEBUG, "left length is not enough, %s.", domain); return -1; } *qtype = _dns_read_short(&context->ptr); *qclass = _dns_read_short(&context->ptr); return 0; } static int _dns_encode_qr_head(struct dns_context *context, char *domain, int qtype, int qclass) { int ret = 0; ret = _dns_encode_domain(context, domain); if (ret < 0) { return -1; } if (_dns_left_len(context) < 4) { return -1; } _dns_write_short(&context->ptr, qtype); _dns_write_short(&context->ptr, qclass); return 0; } static int _dns_decode_rr_head(struct dns_context *context, char *domain, int domain_size, int *qtype, int *qclass, int *ttl, int *rr_len) { int len = 0; len = _dns_decode_qr_head(context, domain, domain_size, qtype, qclass); if (len < 0) { tlog(TLOG_DEBUG, "decode qr head failed."); return -1; } if (_dns_left_len(context) < 6) { tlog(TLOG_DEBUG, "left length is not enough."); return -1; } *ttl = _dns_read_int(&context->ptr); *rr_len = _dns_read_short(&context->ptr); if (*rr_len < 0 || *ttl < 0) { tlog(TLOG_DEBUG, "rr len or ttl is invalid."); return -1; } return 0; } static int _dns_encode_rr_head(struct dns_context *context, char *domain, int qtype, int qclass, int ttl, int rr_len, unsigned char **rr_len_ptr) { int ret = 0; ret = _dns_encode_qr_head(context, domain, qtype, qclass); if (ret < 0) { return -1; } if (_dns_left_len(context) < 6) { return -1; } _dns_write_int(&context->ptr, ttl); if (rr_len_ptr) { *rr_len_ptr = context->ptr; } _dns_write_short(&context->ptr, rr_len); return 0; } static int _dns_encode_raw(struct dns_context *context, struct dns_rrs *rrs) { int ret = 0; int qtype = 0; int qclass = 0; int ttl = 0; char domain[DNS_MAX_CNAME_LEN]; int rr_len = 0; unsigned char *rr_len_ptr = NULL; struct dns_context data_context; /* 0 1 2 3 4 5 6 7 8 9 A B C D E F +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / / / NAME / | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | CLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TTL | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | RDLENGTH | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| / RDATA / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ */ _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 -1; } ret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, rr_len, &rr_len_ptr); if (ret < 0) { return -1; } if (_dns_left_len(context) < rr_len) { return -1; } memcpy(context->ptr, data_context.ptr, rr_len); context->ptr += rr_len; data_context.ptr += rr_len; return 0; } static int _dns_decode_raw(struct dns_context *context, unsigned char *raw, int len) { if (_dns_left_len(context) < len || len < 0) { return -1; } memcpy(raw, context->ptr, len); context->ptr += len; return 0; } static int _dns_decode_CNAME(struct dns_context *context, char *cname, int cname_size) { int ret = 0; ret = _dns_decode_domain(context, cname, cname_size); if (ret < 0) { return -1; } return 0; } static int _dns_encode_CNAME(struct dns_context *context, struct dns_rrs *rrs) { int ret = 0; int qtype = 0; int qclass = 0; int ttl = 0; char domain[DNS_MAX_CNAME_LEN]; int rr_len = 0; unsigned char *rr_len_ptr = NULL; struct dns_context data_context; _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 -1; } /* when code domain, len must plus 1, because of length at the begining */ rr_len = 1; ret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, rr_len, &rr_len_ptr); if (ret < 0) { return -1; } ret = _dns_encode_domain(context, (char *)data_context.ptr); if (ret < 0) { return -1; } rr_len += ret; data_context.ptr += strnlen((char *)(data_context.ptr), DNS_MAX_CNAME_LEN) + 1; if (rr_len > rrs->len) { return -1; } _dns_write_short(&rr_len_ptr, ret); return 0; } static int _dns_decode_SOA(struct dns_context *context, struct dns_soa *soa) { int ret = 0; ret = _dns_decode_domain(context, soa->mname, DNS_MAX_CNAME_LEN - 1); if (ret < 0) { return -1; } ret = _dns_decode_domain(context, soa->rname, DNS_MAX_CNAME_LEN - 1); if (ret < 0) { return -1; } if (_dns_left_len(context) < 20) { return -1; } soa->serial = _dns_read_int(&context->ptr); soa->refresh = _dns_read_int(&context->ptr); soa->retry = _dns_read_int(&context->ptr); soa->expire = _dns_read_int(&context->ptr); soa->minimum = _dns_read_int(&context->ptr); return 0; } static int _dns_encode_SOA(struct dns_context *context, struct dns_rrs *rrs) { int ret = 0; int qtype = 0; int qclass = 0; int ttl = 0; char domain[DNS_MAX_CNAME_LEN]; int rr_len = 0; unsigned char *rr_len_ptr = NULL; struct dns_context data_context; _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 -1; } ret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, rr_len, &rr_len_ptr); if (ret < 0) { return -1; } rr_len = 0; /* mname */ ret = _dns_encode_domain(context, (char *)data_context.ptr); if (ret < 0) { return -1; } rr_len += ret; data_context.ptr += strnlen((char *)(data_context.ptr), DNS_MAX_CNAME_LEN) + 1; /* rname */ ret = _dns_encode_domain(context, (char *)data_context.ptr); if (ret < 0) { return -1; } rr_len += ret; data_context.ptr += strnlen((char *)(data_context.ptr), DNS_MAX_CNAME_LEN) + 1; if (rr_len > rrs->len) { return -1; } rr_len += 20; _dns_write_short(&rr_len_ptr, rr_len); if (_dns_left_len(context) < 20) { return -1; } _dns_write_int(&context->ptr, *(unsigned int *)data_context.ptr); data_context.ptr += 4; _dns_write_int(&context->ptr, *(unsigned int *)data_context.ptr); data_context.ptr += 4; _dns_write_int(&context->ptr, *(unsigned int *)data_context.ptr); data_context.ptr += 4; _dns_write_int(&context->ptr, *(unsigned int *)data_context.ptr); data_context.ptr += 4; _dns_write_int(&context->ptr, *(unsigned int *)data_context.ptr); data_context.ptr += 4; return 0; } static int _dns_decode_opt_ecs(struct dns_context *context, struct dns_opt_ecs *ecs) { // TODO int len = 0; if (_dns_left_len(context) < 4) { return -1; } ecs->family = _dns_read_short(&context->ptr); ecs->source_prefix = _dns_read_char(&context->ptr); ecs->scope_prefix = _dns_read_char(&context->ptr); len = (ecs->source_prefix / 8); len += (ecs->source_prefix % 8 > 0) ? 1 : 0; if (_dns_left_len(context) < len || len > (int)sizeof(ecs->addr)) { return -1; } memcpy(ecs->addr, context->ptr, len); context->ptr += len; tlog(TLOG_DEBUG, "ECS: family:%d, source_prefix:%d, scope_prefix:%d, len:%d", ecs->family, ecs->source_prefix, ecs->scope_prefix, len); tlog(TLOG_DEBUG, "%d.%d.%d.%d", ecs->addr[0], ecs->addr[1], ecs->addr[2], ecs->addr[3]); return 0; } static int _dns_decode_opt_cookie(struct dns_context *context, struct dns_opt_cookie *cookie) { // TODO int len = _dns_left_len(context); if (len < 8) { return -1; } len = 8; memcpy(cookie->client_cookie, context->ptr, len); context->ptr += len; len = _dns_left_len(context); if (len == 0) { cookie->server_cookie_len = 0; return 0; } if (len < 8) { return -1; } memcpy(cookie->server_cookie, context->ptr, len); cookie->server_cookie_len = len; context->ptr += len; tlog(TLOG_DEBUG, "OPT COOKIE"); return 0; } static int _dns_encode_OPT(struct dns_context *context, struct dns_rrs *rrs) { int ret = 0; int opt_code = 0; int qclass = 0; char domain[DNS_MAX_CNAME_LEN]; struct dns_context data_context; int rr_len = 0; int ttl = 0; struct dns_opt *dns_opt = NULL; _dns_init_context_by_rrs(rrs, &data_context); ret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &opt_code, &qclass, &ttl, &rr_len); if (ret < 0) { return -1; } if (rr_len < (int)sizeof(*dns_opt)) { return -1; } if (_dns_left_len(context) < (rr_len)) { return -1; } dns_opt = (struct dns_opt *)data_context.ptr; _dns_write_short(&context->ptr, dns_opt->code); _dns_write_short(&context->ptr, dns_opt->length); if (_dns_left_len(context) < dns_opt->length) { return -1; } switch (dns_opt->code) { case DNS_OPT_T_ECS: { struct dns_opt_ecs *ecs = (struct dns_opt_ecs *)&(dns_opt->data); _dns_write_short(&context->ptr, ecs->family); _dns_write_char(&context->ptr, ecs->source_prefix); _dns_write_char(&context->ptr, ecs->scope_prefix); memcpy(context->ptr, ecs->addr, dns_opt->length - 4); context->ptr += dns_opt->length - 4; } break; default: memcpy(context->ptr, dns_opt->data, dns_opt->length); context->ptr += dns_opt->length; break; } return 0; } static int _dns_get_opts_data_len(struct dns_packet *packet, struct dns_rrs *rrs, int count) { int i = 0; int len = 0; int opt_code = 0; int qclass = 0; int ttl = 0; int ret = 0; char domain[DNS_MAX_CNAME_LEN]; struct dns_context data_context; int rr_len = 0; for (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { _dns_init_context_by_rrs(rrs, &data_context); ret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &opt_code, &qclass, &ttl, &rr_len); if (ret < 0) { return -1; } len += rr_len; } return len; } static int _dns_encode_opts(struct dns_packet *packet, struct dns_context *context, struct dns_rrs *rrs, int count) { int i = 0; int len = 0; int ret = 0; unsigned int rcode = 0; int rr_len = 0; int payloadsize = packet->payloadsize; unsigned char *rr_len_ptr = NULL; rr_len = _dns_get_opts_data_len(packet, rrs, count); if (rr_len < 0) { return -1; } if (payloadsize < DNS_DEFAULT_PACKET_SIZE) { payloadsize = DNS_DEFAULT_PACKET_SIZE; } ret = _dns_encode_rr_head(context, "", DNS_T_OPT, payloadsize, rcode, rr_len, &rr_len_ptr); if (ret < 0) { return -1; } if (_dns_left_len(context) < rr_len) { return -1; } for (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { len = _dns_encode_OPT(context, rrs); if (len < 0) { return -1; } } return 0; } static int _dns_decode_opt(struct dns_context *context, dns_rr_type type, unsigned int ttl, int rr_len) { unsigned short opt_code = 0; unsigned short opt_len = 0; unsigned short ercode = (ttl >> 16) & 0xFFFF; unsigned short ever = (ttl)&0xFFFF; unsigned char *start = context->ptr; struct dns_packet *packet = context->packet; int ret = 0; UNUSED(ever); /* Field Name Field Type Description ------------------------------------------------------ NAME domain name empty (root domain) TYPE u_int16_t OPT CLASS u_int16_t sender's UDP payload size TTL u_int32_t extended RCODE and flags RDLEN u_int16_t describes RDATA RDATA octet stream {attribute,value} pairs +0 (MSB) +1 (LSB) +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0: | OPTION-CODE | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2: | OPTION-LENGTH | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4: | | / OPTION-DATA / / / +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ TTL +0 (MSB) +1 (LSB) +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0: | EXTENDED-RCODE | VERSION | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2: | Z | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ if (rr_len < 0) { tlog(TLOG_DEBUG, "opt len is invalid."); return -1; } if (ercode != 0) { tlog(TLOG_ERROR, "extend rcode invalid."); return -1; } 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; } opt_code = _dns_read_short(&context->ptr); 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); return -1; } tlog(TLOG_DEBUG, "opt type %d", opt_code); switch (opt_code) { case DNS_OPT_T_ECS: { struct dns_opt_ecs ecs; ret = _dns_decode_opt_ecs(context, &ecs); if (ret != 0) { tlog(TLOG_ERROR, "decode ecs failed."); return -1; } ret = dns_add_OPT_ECS(packet, &ecs); if (ret != 0) { tlog(TLOG_ERROR, "add ecs failed."); return -1; } } break; case DNS_OPT_T_COOKIE: { struct dns_opt_cookie cookie; ret = _dns_decode_opt_cookie(context, &cookie); if (ret != 0) { tlog(TLOG_ERROR, "decode cookie failed."); return -1; } } break; case DNS_OPT_T_PADDING: context->ptr += opt_len; break; default: context->ptr += opt_len; tlog(TLOG_DEBUG, "DNS opt type = %d not supported", opt_code); break; } } return 0; } static int _dns_decode_qd(struct dns_context *context) { struct dns_packet *packet = context->packet; int len = 0; int qtype = 0; int qclass = 0; char domain[DNS_MAX_CNAME_LEN]; len = _dns_decode_qr_head(context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass); if (len < 0) { return -1; } len = dns_add_domain(packet, domain, qtype, qclass); if (len < 0) { return -1; } return 0; } static int _dns_decode_an(struct dns_context *context, dns_rr_type type) { int ret = 0; int qtype = 0; int qclass = 0; int ttl = 0; int rr_len = 0; char domain[DNS_MAX_CNAME_LEN]; struct dns_packet *packet = context->packet; unsigned char *start = NULL; /* decode rr head */ ret = _dns_decode_rr_head(context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass, &ttl, &rr_len); if (ret < 0 || qclass < 0) { tlog(TLOG_DEBUG, "decode head failed."); return -1; } start = context->ptr; /* decode answer */ switch (qtype) { case DNS_T_A: { unsigned char addr[DNS_RR_A_LEN]; ret = _dns_decode_raw(context, addr, sizeof(addr)); if (ret < 0) { tlog(TLOG_DEBUG, "decode A failed, %s, len: %d:%d", domain, (int)(context->ptr - context->data), _dns_left_len(context)); return -1; } ret = dns_add_A(packet, type, domain, ttl, addr); if (ret < 0) { tlog(TLOG_ERROR, "add A failed, %s", domain); return -1; } } break; case DNS_T_CNAME: { char cname[DNS_MAX_CNAME_LEN]; ret = _dns_decode_CNAME(context, cname, DNS_MAX_CNAME_LEN); if (ret < 0) { tlog(TLOG_DEBUG, "decode CNAME failed, %s, len: %d:%d", domain, (int)(context->ptr - context->data), _dns_left_len(context)); return -1; } ret = dns_add_CNAME(packet, type, domain, ttl, cname); if (ret < 0) { tlog(TLOG_ERROR, "add CNAME failed, %s", domain); return -1; } } break; case DNS_T_SOA: { struct dns_soa soa; ret = _dns_decode_SOA(context, &soa); if (ret < 0) { tlog(TLOG_DEBUG, "decode SOA failed, %s", domain); return -1; } ret = dns_add_SOA(packet, type, domain, ttl, &soa); if (ret < 0) { tlog(TLOG_ERROR, "add SOA failed, %s", domain); return -1; } } break; case DNS_T_NS: { char ns[DNS_MAX_CNAME_LEN]; ret = _dns_decode_CNAME(context, ns, DNS_MAX_CNAME_LEN); if (ret < 0) { tlog(TLOG_DEBUG, "decode NS failed, %s", domain); return -1; } ret = dns_add_NS(packet, type, domain, ttl, ns); if (ret < 0) { tlog(TLOG_ERROR, "add NS failed, %s", domain); return -1; } } break; case DNS_T_PTR: { char name[DNS_MAX_CNAME_LEN]; ret = _dns_decode_CNAME(context, name, DNS_MAX_CNAME_LEN); if (ret < 0) { tlog(TLOG_DEBUG, "decode PTR failed, %s", domain); return -1; } ret = dns_add_PTR(packet, type, domain, ttl, name); if (ret < 0) { tlog(TLOG_ERROR, "add PTR failed, %s", domain); return -1; } } break; case DNS_T_AAAA: { unsigned char addr[DNS_RR_AAAA_LEN]; ret = _dns_decode_raw(context, addr, sizeof(addr)); if (ret < 0) { tlog(TLOG_DEBUG, "decode AAAA failed, %s", domain); return -1; } ret = dns_add_AAAA(packet, type, domain, ttl, addr); if (ret < 0) { tlog(TLOG_ERROR, "add AAAA failed, %s", domain); return -1; } } break; case DNS_T_OPT: { unsigned char *opt_start = context->ptr; ret = _dns_decode_opt(context, type, ttl, rr_len); if (ret < 0) { tlog(TLOG_DEBUG, "decode opt failed, %s", domain); return -1; } if (context->ptr - opt_start != rr_len) { tlog(TLOG_DEBUG, "opt length mismatch, %s\n", domain); return -1; } dns_set_OPT_payload_size(packet, qclass); } break; default: { unsigned char raw_data[1024]; if (_dns_left_len(context) < rr_len || rr_len >= (int)sizeof(raw_data)) { tlog(TLOG_DEBUG, "length mismatch\n"); return -1; } ret = _dns_decode_raw(context, raw_data, rr_len); if (ret < 0) { tlog(TLOG_DEBUG, "decode A failed, %s", domain); return -1; } ret = _dns_add_RAW(packet, type, qtype, domain, ttl, raw_data, rr_len); if (ret < 0) { tlog(TLOG_ERROR, "add raw failed, %s", domain); return -1; } tlog(TLOG_DEBUG, "DNS type = %d not supported", qtype); break; } } if (context->ptr - start != rr_len) { tlog(TLOG_ERROR, "length mismatch, %s, %ld:%d", domain, (long)(context->ptr - start), rr_len); return -1; } return 0; } static int _dns_encode_qd(struct dns_context *context, struct dns_rrs *rrs) { int ret = 0; int qtype = 0; int qclass = 0; char domain[DNS_MAX_CNAME_LEN]; struct dns_context data_context; _dns_init_context_by_rrs(rrs, &data_context); ret = _dns_get_qr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass); if (ret < 0) { return -1; } ret = _dns_encode_qr_head(context, domain, qtype, qclass); if (ret < 0) { return -1; } return 0; } static int _dns_encode_an(struct dns_context *context, struct dns_rrs *rrs) { int ret = 0; switch (rrs->type) { case DNS_T_A: case DNS_T_AAAA: { ret = _dns_encode_raw(context, rrs); if (ret < 0) { return -1; } } break; case DNS_T_CNAME: case DNS_T_PTR: ret = _dns_encode_CNAME(context, rrs); if (ret < 0) { return -1; } break; case DNS_T_SOA: ret = _dns_encode_SOA(context, rrs); if (ret < 0) { return -1; } break; default: ret = _dns_encode_raw(context, rrs); if (ret < 0) { return -1; } break; } return 0; } static int _dns_decode_body(struct dns_context *context) { struct dns_packet *packet = context->packet; struct dns_head *head = &packet->head; int i = 0; int ret = 0; int count = 0; count = head->qdcount; head->qdcount = 0; for (i = 0; i < count; i++) { ret = _dns_decode_qd(context); if (ret < 0) { tlog(TLOG_DEBUG, "decode qd failed."); return -1; } } count = head->ancount; head->ancount = 0; for (i = 0; i < count; i++) { ret = _dns_decode_an(context, DNS_RRS_AN); if (ret < 0) { tlog(TLOG_DEBUG, "decode an failed."); return -1; } } count = head->nscount; head->nscount = 0; for (i = 0; i < count; i++) { ret = _dns_decode_an(context, DNS_RRS_NS); if (ret < 0) { tlog(TLOG_DEBUG, "decode ns failed."); return -1; } } count = head->nrcount; head->nrcount = 0; for (i = 0; i < count; i++) { ret = _dns_decode_an(context, DNS_RRS_NR); if (ret < 0) { tlog(TLOG_DEBUG, "decode nr failed."); return -1; } } return 0; } static int _dns_encode_body(struct dns_context *context) { struct dns_packet *packet = context->packet; struct dns_head *head = &packet->head; int i = 0; int len = 0; struct dns_rrs *rrs = NULL; int count = 0; rrs = dns_get_rrs_start(packet, DNS_RRS_QD, &count); head->qdcount = count; for (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { len = _dns_encode_qd(context, rrs); if (len < 0) { return -1; } } rrs = dns_get_rrs_start(packet, DNS_RRS_AN, &count); head->ancount = count; for (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { len = _dns_encode_an(context, rrs); if (len < 0) { return -1; } } rrs = dns_get_rrs_start(packet, DNS_RRS_NS, &count); head->nscount = count; for (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { len = _dns_encode_an(context, rrs); if (len < 0) { return -1; } } rrs = dns_get_rrs_start(packet, DNS_RRS_NR, &count); head->nrcount = count; for (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) { len = _dns_encode_an(context, rrs); if (len < 0) { return -1; } } rrs = dns_get_rrs_start(packet, DNS_RRS_OPT, &count); if (count > 0 || packet->payloadsize > 0) { len = _dns_encode_opts(packet, context, rrs, count); if (len < 0) { return -1; } head->nrcount++; } return 0; } int dns_packet_init(struct dns_packet *packet, int size, struct dns_head *head) { struct dns_head *init_head = &packet->head; if (size < (int)sizeof(*packet)) { return -1; } memset(packet, 0, size); packet->size = size; init_head->id = head->id; init_head->qr = head->qr; init_head->opcode = head->opcode; init_head->aa = head->aa; init_head->tc = head->tc; init_head->rd = head->rd; init_head->ra = head->ra; init_head->rcode = head->rcode; packet->questions = DNS_RR_END; packet->answers = DNS_RR_END; packet->nameservers = DNS_RR_END; packet->additional = DNS_RR_END; packet->optional = DNS_RR_END; packet->optcount = 0; packet->payloadsize = 0; return 0; } int dns_decode(struct dns_packet *packet, int maxsize, unsigned char *data, int size) { struct dns_head *head = &packet->head; struct dns_context context; int ret = 0; memset(&context, 0, sizeof(context)); memset(packet, 0, sizeof(*packet)); context.data = data; context.packet = packet; context.ptr = data; context.maxsize = size; context.namedict = &packet->namedict; ret = dns_packet_init(packet, maxsize, head); if (ret != 0) { return -1; } ret = _dns_decode_head(&context); if (ret < 0) { return -1; } ret = _dns_decode_body(&context); if (ret < 0) { tlog(TLOG_DEBUG, "decode body failed.\n"); return -1; } packet->size = context.ptr - context.data + sizeof(*packet); return 0; } int dns_encode(unsigned char *data, int size, struct dns_packet *packet) { int ret = 0; struct dns_context context; struct dns_packet_dict namedict; memset(&context, 0, sizeof(context)); memset(&namedict, 0, sizeof(namedict)); context.data = data; context.packet = packet; context.ptr = data; context.maxsize = size; context.namedict = &namedict; ret = _dns_encode_head(&context); if (ret < 0) { return -1; } ret = _dns_encode_body(&context); if (ret < 0) { return -1; } ret = _dns_encode_head_count(&context); if (ret < 0) { return -1; } return context.ptr - context.data; } static int _dns_update_an(struct dns_context *context, dns_rr_type type, struct dns_update_param *param) { int ret = 0; int qtype = 0; int qclass = 0; int ttl = 0; int rr_len = 0; char domain[DNS_MAX_CNAME_LEN]; unsigned char *start = NULL; /* decode rr head */ ret = _dns_decode_rr_head(context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass, &ttl, &rr_len); if (ret < 0) { tlog(TLOG_DEBUG, "decode head failed."); return -1; } start = context->ptr; switch (qtype) { case DNS_T_OPT: break; default: { unsigned char *ttl_ptr = start - sizeof(int) - sizeof(short); if (param->ip_ttl < 0) { break; } _dns_write_int(&ttl_ptr, param->ip_ttl); } break; } context->ptr += rr_len; if (context->ptr - start != rr_len) { tlog(TLOG_ERROR, "length mismatch , %s, %ld:%d", domain, (long)(context->ptr - start), rr_len); return -1; } return 0; } static int _dns_update_body(struct dns_context *context, struct dns_update_param *param) { struct dns_packet *packet = context->packet; struct dns_head *head = &packet->head; int i = 0; int ret = 0; int count = 0; count = head->qdcount; head->qdcount = 0; for (i = 0; i < count; i++) { char domain[DNS_MAX_CNAME_LEN]; int qtype = 0; int qclass = 0; int len = 0; len = _dns_decode_qr_head(context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass); if (len < 0) { tlog(TLOG_DEBUG, "update qd failed."); return -1; } } count = head->ancount; head->ancount = 0; for (i = 0; i < count; i++) { ret = _dns_update_an(context, DNS_RRS_AN, param); if (ret < 0) { tlog(TLOG_DEBUG, "update an failed."); return -1; } } count = head->nscount; head->nscount = 0; for (i = 0; i < count; i++) { ret = _dns_update_an(context, DNS_RRS_NS, param); if (ret < 0) { tlog(TLOG_DEBUG, "update ns failed."); return -1; } } count = head->nrcount; head->nrcount = 0; for (i = 0; i < count; i++) { ret = _dns_update_an(context, DNS_RRS_NR, param); if (ret < 0) { tlog(TLOG_DEBUG, "update nr failed."); return -1; } } return 0; } static int _dns_update_id(unsigned char *data, int size, struct dns_update_param *param) { unsigned char *ptr = data; _dns_write_short(&ptr, param->id); return 0; } int dns_packet_update(unsigned char *data, int size, struct dns_update_param *param) { struct dns_packet packet; int maxsize = sizeof(packet); struct dns_head *head = &packet.head; struct dns_context context; int ret = 0; memset(&context, 0, sizeof(context)); memset(&packet, 0, sizeof(packet)); context.data = data; context.packet = &packet; context.ptr = data; context.maxsize = size; ret = dns_packet_init(&packet, maxsize, head); if (ret != 0) { return -1; } ret = _dns_decode_head(&context); if (ret < 0) { return -1; } ret = _dns_update_id(data, size, param); if (ret < 0) { return -1; } ret = _dns_update_body(&context, param); if (ret < 0) { tlog(TLOG_DEBUG, "decode body failed.\n"); return -1; } return 0; }