slirp: resolve .local suffix DNS requests using the host's name resolution

(cherry picked from commit e2a6a4c1177e252bc38221c87bb99a599e8761a1)
This commit is contained in:
rakslice 2020-11-14 02:16:41 -08:00
parent d547db0648
commit a453ae105a

View File

@ -14,6 +14,26 @@
#include <sys/filio.h>
#endif
#define DEBUG_HOST_RESOLVED_DNS 0
/**
* DNS requests for these domain suffixes will be
* looked up on the host to allow for host-supported DNS alternatives
* (e.g. MDNS, hosts file, etc.)
**/
static const char * host_resolved_domain_suffixes[] = {
".local.",
NULL // list ending
};
#define DOT_LOCAL_TTL 60 // In seconds. Keep this short as data is very dynamic and requerying is cheap
#if DEBUG_HOST_RESOLVED_DNS
#define D(...) printf(__VA_ARGS__); fflush(stdout);
#else
#define D(...)
#endif
void
so_init()
{
@ -482,6 +502,270 @@ sorecvfrom(so)
} /* if ping packet */
}
struct DNS_HEADER
{
unsigned short id; // identification number
unsigned char rd :1; // recursion desired
unsigned char tc :1; // truncated message
unsigned char aa :1; // authoritive answer
unsigned char opcode :4; // purpose of message
unsigned char qr :1; // query/response flag
unsigned char rcode :4; // response code
unsigned char cd :1; // checking disabled
unsigned char ad :1; // authenticated data
unsigned char z :1; // its z! reserved
unsigned char ra :1; // recursion available
unsigned short q_count; // number of question entries
unsigned short ans_count; // number of answer entries
unsigned short auth_count; // number of authority entries
unsigned short add_count; // number of resource entries
};
struct QUESTION
{
unsigned short qtype;
unsigned short qclass;
};
#pragma pack(push, 1)
struct R_DATA
{
unsigned short type;
unsigned short _class;
unsigned int ttl;
unsigned short data_len;
};
#pragma pack(pop)
#define POP_STRUCT(vartype, varname, data, len) \
assert(len >= sizeof(vartype)); \
vartype varname; \
memcpy(&varname, data, sizeof(vartype)); \
data += sizeof(vartype); \
len -= sizeof(vartype)
static void inject_udp_packet_to_guest(struct socket * so, struct sockaddr_in addr, caddr_t packet_data, int packet_len) {
struct mbuf *m;
int len;
if (!(m = m_get())) return;
m->m_data += if_maxlinkhdr;
len = M_FREEROOM(m);
if (packet_len > len) {
packet_len = (m->m_data - m->m_dat) + m->m_len + packet_len + 1;
m_inc(m, packet_len);
len = M_FREEROOM(m);
}
assert(len >= packet_len);
m->m_len = packet_len;
memcpy(m->m_data, packet_data, packet_len);
udp_output(so, m, &addr);
}
/* Decode hostname from the format used in DNS
e.g. "\009something\004else\003com" for "something.else.com." */
static char * decode_dns_name(char * data) {
int query_str_len = strlen(data);
char * decoded_name_str = malloc(query_str_len + 1);
if (decoded_name_str == NULL) {
D("decode_dns_name(): out of memory\n");
return NULL; // oom
}
char * decoded_name_str_pos = decoded_name_str;
while (*data != '\0') {
int part_len = *data++;
query_str_len--;
if (query_str_len < part_len) {
D("decode_dns_name(): part went off the end of the string\n");
free(decoded_name_str);
return NULL;
}
memcpy(decoded_name_str_pos, data, part_len);
decoded_name_str_pos[part_len] = '.';
decoded_name_str_pos += part_len + 1;
query_str_len -= part_len;
data += part_len;
}
*decoded_name_str_pos = '\0';
return decoded_name_str;
}
/** Take a look at a UDP DNS request the client has made and see if we want to resolve it internally.
* Returns TRUE if the request has been internally and can be dropped,
* FALSE otherwise
**/
static BOOL resolve_dns_request(struct socket * so, struct sockaddr_in addr, caddr_t data, int len) {
BOOL drop_dns_request = FALSE;
D("Checking outgoing DNS UDP packet\n");
if (len < sizeof(struct DNS_HEADER)) {
D("Packet too short for DNS header\n");
return FALSE;
}
const caddr_t packet = data;
const int packet_len = len;
POP_STRUCT(struct DNS_HEADER, h, data, len);
if (h.qr != 0) {
D("DNS packet is not a request\n");
return FALSE;
}
if (ntohs(h.q_count) == 0) {
D("DNS request has no queries\n");
return FALSE;
}
if (ntohs(h.q_count) > 1) {
D("DNS request has multiple queries (not supported)\n");
return FALSE;
}
if (ntohs(h.ans_count != 0) || ntohs(h.auth_count != 0) || ntohs(h.add_count != 0)) {
D("DNS request has unsupported extra contents\n");
return FALSE;
}
if (len == 0) {
D("Packet too short for DNS query string\n");
return FALSE;
}
char * original_query_str = data;
int query_str_len = strnlen(data, len);
if (query_str_len == len) { // went off end of packet
D("Unterminated DNS query string\n");
return FALSE;
}
char * decoded_name_str = decode_dns_name(original_query_str);
if (decoded_name_str == NULL) {
D("Error while decoding DNS query string");
return FALSE;
}
D("DNS host query for %s\n", decoded_name_str);
data += query_str_len + 1;
len -= query_str_len + 1;
POP_STRUCT(struct QUESTION, qinfo, data, len);
if (ntohs(qinfo.qtype) != 1 || ntohs(qinfo.qclass) != 1) {
D("DNS host query for %s: Request isn't the supported type (INET A query)\n", decoded_name_str);
free(decoded_name_str);
return FALSE;
}
D("DNS host query for %s: Request is eligible to check for host resolution suffix\n", decoded_name_str);
const char * matched_suffix = NULL;
for (const char ** suffix_ptr = host_resolved_domain_suffixes; *suffix_ptr != NULL; suffix_ptr++) {
const char * suffix = *suffix_ptr;
int suffix_pos = strlen(decoded_name_str) - strlen(suffix);
if (suffix_pos > 0 && strcmp(decoded_name_str + suffix_pos, suffix) == 0) {
matched_suffix = suffix;
break;
}
}
if (matched_suffix == NULL) {
D("DNS host query for %s: No suffix matched\n", decoded_name_str);
} else {
D("DNS host query for %s: Suffix matched: %s\n", decoded_name_str, matched_suffix);
// we are going to take this request and resolve it on the host
drop_dns_request = TRUE;
D("DNS host query for %s: Doing lookup on host\n", decoded_name_str);
int results_count = 0;
struct hostent * host_lookup_result = gethostbyname(decoded_name_str);
if (host_lookup_result && host_lookup_result->h_addrtype == AF_INET) {
D("DNS host query for %s: Host response has results for AF_INET\n", decoded_name_str);
for (char ** addr_entry = host_lookup_result->h_addr_list; *addr_entry != NULL; addr_entry++) {
++results_count;
}
}
D("DNS host query for %s: result count %d\n", decoded_name_str, results_count);
int original_query_str_size = strlen(original_query_str) + 1;
int response_size = packet_len + results_count * (original_query_str_size + sizeof(struct R_DATA) + sizeof(struct in_addr));
caddr_t response_packet = malloc(response_size);
if (response_packet == NULL) {
D("DNS host query for %s: Out of memory while allocating DNS response packet\n", decoded_name_str);
} else {
D("DNS host query for %s: Preparing DNS response\n", decoded_name_str);
// use the request DNS header as our starting point for the response
h.qr = 1;
h.ans_count = htons(results_count);
memcpy(response_packet, &h, sizeof(struct DNS_HEADER));
// other sections verbatim out of the request
memcpy(response_packet + sizeof(struct DNS_HEADER), packet + sizeof(struct DNS_HEADER), packet_len - sizeof(struct DNS_HEADER));
int response_pos = packet_len;
if (results_count > 0) {
for (char ** addr_entry = host_lookup_result->h_addr_list; *addr_entry != NULL; addr_entry++) {
// answer string is verbatim from question
memcpy(response_packet + response_pos, original_query_str, original_query_str_size);
response_pos += original_query_str_size;
struct R_DATA resource;
resource.type = htons(1);
resource._class = htons(1);
resource.ttl = htonl(DOT_LOCAL_TTL);
resource.data_len = htons(sizeof(struct in_addr));
memcpy(response_packet + response_pos, &resource, sizeof(struct R_DATA));
response_pos += sizeof(struct R_DATA);
struct in_addr * cur_addr = (struct in_addr *)*addr_entry;
memcpy(response_packet + response_pos, cur_addr, sizeof(struct in_addr));
response_pos += sizeof(struct in_addr);
}
assert(response_pos == response_size);
}
D("DNS host query for %s: Injecting DNS response directly to guest\n", decoded_name_str);
inject_udp_packet_to_guest(so, addr, response_packet, response_size);
free(response_packet);
}
}
free(decoded_name_str);
D("DNS host request drop: %s\n", drop_dns_request? "yes" : "no");
return drop_dns_request;
}
/*
* sendto() a socket
*/
@ -503,6 +787,8 @@ sosendto(so, m)
switch(ntohl(so->so_faddr.s_addr) & 0xff) {
case CTL_DNS:
addr.sin_addr = dns_addr;
if (resolve_dns_request(so, addr, m->m_data, m->m_len))
return 0;
break;
case CTL_ALIAS:
default: