Skip to content

Commit

Permalink
Recursive DNS implementation (#286)
Browse files Browse the repository at this point in the history
* flush system DNS cache after setting Ziti DNS

* replace DNS fallback with recursion

* fix potential dns_req leak

* update ziti sdk

* fix potential memmory issues

* add command line option to specify upstream DNS

* update README

* cleanup obsolete code
  • Loading branch information
ekoby authored Feb 7, 2022
1 parent 8c61359 commit 55af716
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 185 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# Release v0.17.23

## What's new

* Tunneler SDK: Recursive DNS support -- DNS queries not matched by Ziti services are forwarded to upstream server (if configured)
* `ziti-edge-tunnel`
* add `-u|--dns-upstream` option for setting DNS upstream server
* `-n/--dns` option is removed

# Release v0.17.21

## What's new
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.14)
if(NOT ZITI_SDK_C_BRANCH)
#allow using a different branch of the CSDK easily
set(ZITI_SDK_C_BRANCH "0.26.22")
set(ZITI_SDK_C_BRANCH "0.26.23")
endif()

option(TUNNEL_SDK_ONLY "build only ziti-tunnel-sdk (without ziti)" OFF)
Expand Down
12 changes: 1 addition & 11 deletions lib/ziti-tunnel-cbs/include/ziti/ziti_dns.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,13 @@
#define DNS_REFUSE 5
#define DNS_NOTZONE 9

typedef struct dns_manager_s dns_manager;

typedef int (*dns_fallback_cb)(const char *name, void *ctx, struct in_addr* addr);

int ziti_dns_setup(tunneler_context tnlr, const char *dns_addr, const char *dns_cidr);

void ziti_dns_set_fallback(struct uv_loop_s *l, dns_fallback_cb fb, void *ctx);
void ziti_dns_set_manager(dns_manager *mgr);
int ziti_dns_set_upstream(uv_loop_t *l, const char *host, uint16_t port);

const char *ziti_dns_register_hostname(const char *hostname, void *intercept);
const char *ziti_dns_reverse_lookup(const char *ip_addr);

void ziti_dns_deregister_intercept(void *intercept);

struct dns_manager_s {
int (*apply)(dns_manager *dns, const char *host, const char *ip);
int (*remove)(dns_manager *dns, const char *host);
void *data;
};
#endif //ZITI_TUNNEL_SDK_C_ZITI_DNS_H
240 changes: 155 additions & 85 deletions lib/ziti-tunnel-cbs/ziti_dns.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,20 @@ typedef struct ziti_dns_client_s {
} ziti_dns_client_t;

struct dns_req {
uint16_t id;
size_t req_len;
uint8_t req[512];
size_t resp_len;
uint8_t resp[512];

char host[255];
uint16_t q_class;
uint16_t q_type;

dns_fallback_cb fallback;
void *fb_ctx;

struct in_addr addr;
int code;
int rr_count;

uint8_t resp[512];
uint8_t *rp;

ziti_dns_client_t *clt;
Expand All @@ -58,7 +60,10 @@ struct dns_req {
static void* on_dns_client(const void *app_intercept_ctx, io_ctx_t *io);
static int on_dns_close(void *dns_io_ctx);
static ssize_t on_dns_req(void *ziti_io_ctx, void *write_ctx, const uint8_t *q_packet, size_t len);

static void query_upstream(struct dns_req *req);
static void udp_alloc(uv_handle_t *h, unsigned long reqlen, uv_buf_t *b);
static void on_upstream_packet(uv_udp_t *h, ssize_t rc, const uv_buf_t *buf, const struct sockaddr* addr, unsigned int flags);
static void complete_dns_req(struct dns_req *req);

// hostname or domain
typedef struct dns_entry_s {
Expand Down Expand Up @@ -86,10 +91,11 @@ struct ziti_dns_s {
// map[domain -> dns_entry_t]
model_map domains;

dns_fallback_cb fallback_cb;
void * fallback_ctx;
uv_loop_t *loop;
tunneler_context tnlr;

model_map requests;
uv_udp_t upstream;
} ziti_dns;

static uint32_t next_ipv4() {
Expand Down Expand Up @@ -145,10 +151,29 @@ int ziti_dns_setup(tunneler_context tnlr, const char *dns_addr, const char *dns_
return 0;
}

void ziti_dns_set_fallback(uv_loop_t *loop, dns_fallback_cb fb, void *ctx) {
ziti_dns.loop = loop;
ziti_dns.fallback_cb = fb;
ziti_dns.fallback_ctx = ctx;
#define CHECK_UV(op) do{ int rc = (op); if (rc < 0) {\
ZITI_LOG(ERROR, "failed [" #op "]: %d(%s)", rc, uv_strerror(rc)); \
return rc;} \
}while(0)

int ziti_dns_set_upstream(uv_loop_t *l, const char *host, uint16_t port) {
if (uv_is_active((const uv_handle_t *) &ziti_dns.upstream)) {
uv_udp_recv_stop(&ziti_dns.upstream);
CHECK_UV(uv_udp_connect(&ziti_dns.upstream, NULL));
} else {
CHECK_UV(uv_udp_init(l, &ziti_dns.upstream));
}

if (port == 0) port = 53;

char port_str[6];
snprintf(port_str, sizeof(port_str), "%d", port);
uv_getaddrinfo_t req = {0};
CHECK_UV(uv_getaddrinfo(l, &req, NULL, host, port_str, NULL));
CHECK_UV(uv_udp_connect(&ziti_dns.upstream, req.addrinfo->ai_addr));
CHECK_UV(uv_udp_recv_start(&ziti_dns.upstream, udp_alloc, on_upstream_packet));
ZITI_LOG(INFO, "DNS upstream is set to %s:%d", host, port);
return 0;
}

void* on_dns_client(const void *app_intercept_ctx, io_ctx_t *io) {
Expand Down Expand Up @@ -322,98 +347,96 @@ const char *ziti_dns_register_hostname(const char *hostname, void *intercept) {
static const char DNS_OPT[] = { 0x0, 0x0, 0x29, 0x02, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };


#define DNS_ID(p) ((p)[0] << 8 | (p)[1])
#define DNS_ID(p) ((uint8_t)(p)[0] << 8 | (uint8_t)(p)[1])
#define DNS_FLAGS(p) ((p)[2] << 8 | (p)[3])
#define DNS_QRS(p) ((p)[4] << 8 | (p)[5])
#define DNS_QR(p) ((p) + 12)
#define DNS_RD(p) ((p)[2] & 0x1)

#define DNS_SET_RA(p) ((p)[3] = (p)[3] | 0x80)
#define DNS_SET_CODE(p,c) ((p)[3] = (p)[3] | ((c) & 0xf))
#define DNS_SET_ANS(p) ((p)[2] = (p)[2] | 0x80)
#define DNS_SET_ARS(p,n) do{ (p)[6] = (n) >> 8; (p)[7] = (n) & 0xff; } while(0)
#define DNS_SET_AARS(p,n) do{ (p)[10] = (n) >> 8; (p)[11] = (n) & 0xff; } while(0)

#define IS_QUERY(flags) (((flags) & (1 << 15)) == 0)

static void format_resp(struct dns_req *req) {

static void fallback_work(uv_work_t *work_req) {
struct dns_req *f_req = work_req->data;
f_req->code = f_req->fallback(f_req->host, f_req->fb_ctx, &f_req->addr);
}

static void dns_work_complete(uv_work_t *work_req, int status) {
struct dns_req *req = work_req->data;
if (req->clt != NULL) {
LIST_REMOVE(req, _next);

uint8_t *rp = req->rp;
DNS_SET_CODE(req->resp, req->code);
if (req->code == DNS_NO_ERROR && req->rr_count > 0) {
ZITI_LOG(TRACE, "found record for host[%s]", req->host);
DNS_SET_ARS(req->resp, 1);
uint8_t *rp = req->rp;
DNS_SET_CODE(req->resp, req->code);
if (req->code == DNS_NO_ERROR && req->rr_count > 0) {
ZITI_LOG(TRACE, "found record for host[%s]", req->host);
DNS_SET_ARS(req->resp, 1);

// name ref
*rp++ = 0xc0;
*rp++ = 0x0c;
// name ref
*rp++ = 0xc0;
*rp++ = 0x0c;

// type A
*rp++ = 0;
*rp++ = 1;
// type A
*rp++ = 0;
*rp++ = 1;

// class IN
*rp++ = 0;
*rp++ = 1;
// class IN
*rp++ = 0;
*rp++ = 1;

// TTL
*rp++ = 0;
*rp++ = 0;
*rp++ = 0;
*rp++ = 255;
// TTL
*rp++ = 0;
*rp++ = 0;
*rp++ = 0;
*rp++ = 255;

// size 4
*rp++ = 0;
*rp++ = sizeof(req->addr.s_addr);
// size 4
*rp++ = 0;
*rp++ = sizeof(req->addr.s_addr);

memcpy(rp, &req->addr.s_addr, sizeof(req->addr.s_addr));
rp += sizeof(req->addr.s_addr);
} else {
DNS_SET_ARS(req->resp, 0);
}

DNS_SET_AARS(req->resp, 1);
memcpy(rp, DNS_OPT, sizeof(DNS_OPT));
rp += sizeof(DNS_OPT);

ziti_tunneler_write(req->clt->io_ctx->tnlr_io, req->resp, rp - req->resp);
memcpy(rp, &req->addr.s_addr, sizeof(req->addr.s_addr));
rp += sizeof(req->addr.s_addr);
} else {
ZITI_LOG(DEBUG, "DNS request[%s] completed for closed client", req->host);
DNS_SET_ARS(req->resp, 0);
}
free(req);
free(work_req);

DNS_SET_AARS(req->resp, 1);
memcpy(rp, DNS_OPT, sizeof(DNS_OPT));
rp += sizeof(DNS_OPT);
req->resp_len = rp - req->resp;
}

ssize_t on_dns_req(void *ziti_io_ctx, void *write_ctx, const uint8_t *q_packet, size_t q_len) {
struct dns_req *req = calloc(1, sizeof(struct dns_req));
req->clt = ziti_io_ctx;
LIST_INSERT_HEAD(&req->clt->active_reqs, req, _next);

uv_work_t *work_req = calloc(1, sizeof(uv_work_t));
work_req->data = req;

memcpy(req->resp, q_packet, 12); // DNS header
DNS_SET_ANS(req->resp);
uint8_t *rp = req->resp + 12;
bool recursion_avail = uv_is_active((const uv_handle_t *) &ziti_dns.upstream);

uint16_t flags = DNS_FLAGS(q_packet);
uint16_t qrrs = DNS_QRS(q_packet);
int recursive = DNS_RD(q_packet);

req->id = DNS_ID(q_packet);
req->req_len = q_len;
memcpy(req->req, q_packet, q_len);
model_map_setl(&ziti_dns.requests, (long)req->id, req);
ZITI_LOG(TRACE, "received DNS query q_len=%zd id[%04x] flags[%04x] recursive[%s]", q_len, req->id, flags, recursive ? "true" : "false");

memcpy(req->resp, q_packet, 12); // DNS header
DNS_SET_ANS(req->resp);
if (recursion_avail) {
DNS_SET_RA(req->resp);
}
DNS_SET_ARS(req->resp, 0);
DNS_SET_AARS(req->resp, 0);
DNS_SET_CODE(req->resp, DNS_NO_ERROR);

ZITI_LOG(TRACE, "received DNS query q_len=%zd id(%04x) flags(%04x)", q_len, DNS_ID(q_packet), DNS_FLAGS(q_packet));
uint8_t *rp = req->resp + 12;

if (!IS_QUERY(flags) || qrrs != 1) {
DNS_SET_ARS(req->resp, 0);
DNS_SET_AARS(req->resp, 0);
DNS_SET_CODE(req->resp, DNS_NOT_IMPL);
req->code = DNS_NOT_IMPL;

complete_dns_req(req);
goto DONE;
}

Expand Down Expand Up @@ -445,23 +468,14 @@ ssize_t on_dns_req(void *ziti_io_ctx, void *write_ctx, const uint8_t *q_packet,
DNS_SET_AARS(req->resp, 0);
DNS_SET_CODE(req->resp, DNS_NOT_IMPL);
req->code = DNS_NOT_IMPL;

complete_dns_req(req);
goto DONE;
}

req->rp = rp;

if (req->q_type == NS_T_A || req->q_type == NS_T_AAAA) {
dns_entry_t *entry = ziti_dns_lookup(req->host);
if (!entry && ziti_dns.fallback_cb) {
req->fb_ctx = ziti_dns.fallback_ctx;
req->fallback = ziti_dns.fallback_cb;

ziti_tunneler_ack(write_ctx);
uv_queue_work(ziti_dns.loop, work_req, fallback_work, dns_work_complete);
return q_len;
}

if (entry) {
req->code = DNS_NO_ERROR;
if (req->q_type == NS_T_A) {
Expand All @@ -473,19 +487,75 @@ ssize_t on_dns_req(void *ziti_io_ctx, void *write_ctx, const uint8_t *q_packet,
DNS_SET_CODE(req->resp, DNS_NO_ERROR);
req->rr_count = 0;
}
} else {
req->code = DNS_NXDOMAIN;
format_resp(req);
} else {
if (recursive && recursion_avail) {
query_upstream(req);
goto DONE;
}
}
} else {
// future proxy lookup for (MX, TXT, SRV)
DNS_SET_ARS(req->resp, 0);
DNS_SET_AARS(req->resp, 0);
DNS_SET_CODE(req->resp, DNS_NO_ERROR);
}

// future proxy lookup for (MX, TXT, SRV)
complete_dns_req(req);

DONE:
ziti_tunneler_ack(write_ctx);
dns_work_complete(work_req, 0);

return q_len;
return (ssize_t)q_len;
}

static void on_upstream_send(uv_udp_send_t *sr, int rc) {
struct dns_req *req = sr->data;
if (rc < 0) {
ZITI_LOG(WARN, "failed to query[%04x] upstream DNS server: %d(%s)", req->id, rc, uv_strerror(rc));
}
free(sr);
}

void query_upstream(struct dns_req *req) {
int rc;
uv_udp_send_t *sr = calloc(1, sizeof(uv_udp_send_t));
sr->data = req;
uv_buf_t buf = uv_buf_init((char*)req->req, req->req_len);
if ((rc = uv_udp_send(sr, &ziti_dns.upstream, &buf, 1, NULL, on_upstream_send)) != 0) {
ZITI_LOG(WARN, "failed to query[%04x] upstream DNS server: %d(%s)", req->id, rc, uv_strerror(rc));
}
}

static void udp_alloc(uv_handle_t *h, unsigned long reqlen, uv_buf_t *b) {
b->base = malloc(1024);
b->len = 1024;
}

static void on_upstream_packet(uv_udp_t *h, ssize_t rc, const uv_buf_t *buf, const struct sockaddr* addr, unsigned int flags) {
if (rc > 0) {
uint16_t id = DNS_ID(buf->base);
struct dns_req *req = model_map_getl(&ziti_dns.requests, id);
if (req == NULL) {
ZITI_LOG(WARN, "got response for unknown query[%04x] (rc=%zd)", id, rc);
} else {
ZITI_LOG(TRACE, "upstream sent response to query[%04x] (rc=%zd)", id, rc);
if (rc <= sizeof(req->resp)) {
req->resp_len = rc;
memcpy(req->resp, buf->base, rc);
} else {
ZITI_LOG(WARN, "unexpected DNS response: too large");
}
complete_dns_req(req);
}
}
free(buf->base);
}

static void complete_dns_req(struct dns_req *req) {
model_map_removel(&ziti_dns.requests, req->id);
if (req->clt) {
ziti_tunneler_write(req->clt->io_ctx->tnlr_io, req->resp, req->resp_len);
LIST_REMOVE(req, _next);
} else {
ZITI_LOG(WARN, "query[%04x] is stale", req->id);
}

free(req);
}
Loading

0 comments on commit 55af716

Please sign in to comment.