diff --git a/src/context.c b/src/context.c
index eb242be0..87071f7b 100644
--- a/src/context.c
+++ b/src/context.c
@@ -54,6 +54,7 @@
 #include "dnssec.h"
 #include "stub.h"
 #include "list.h"
+#include "dict.h"
 
 #define GETDNS_PORT_ZERO 0
 #define GETDNS_PORT_DNS 53
@@ -167,7 +168,7 @@ static inline void canonicalize_dname(uint8_t *dname)
 {
 	uint8_t *next_label;
 
-	while (*dname) {
+	while (*dname && !(*dname & 0xC0)) {
 		next_label = dname + *dname + 1;
 		dname += 1;
 		while (dname < next_label) {
@@ -599,6 +600,64 @@ net_req_query_id_cmp(const void *id1, const void *id2)
 	return (intptr_t)id1 - (intptr_t)id2;
 }
 
+static getdns_tsig_info tsig_info[] = {
+	  { GETDNS_NO_TSIG, NULL, 0, NULL, 0, 0, 0 }
+	, { GETDNS_HMAC_MD5   , "hmac-md5.sig-alg.reg.int", 24
+	       , (uint8_t *)"\x08hmac-md5\x07sig-alg\x03reg\x03int", 26, 10, 16 }
+	, { GETDNS_NO_TSIG, NULL, 0, NULL, 0, 0, 0 }
+	, { GETDNS_HMAC_SHA1  , "hmac-sha1"  ,  9
+	       , (uint8_t *)"\x09hmac-sha1"  , 11, 10, 20 }
+	, { GETDNS_HMAC_SHA224, "hmac-sha224", 11
+	       , (uint8_t *)"\x0bhmac-sha224", 13, 14, 28 }
+	, { GETDNS_HMAC_SHA224, "hmac-sha256", 11
+	       , (uint8_t *)"\x0bhmac-sha256", 13, 16, 32 }
+	, { GETDNS_HMAC_SHA224, "hmac-sha384", 11
+	       , (uint8_t *)"\x0bhmac-sha383", 13, 24, 48 }
+	, { GETDNS_HMAC_SHA224, "hmac-sha512", 11
+	       , (uint8_t *)"\x0bhmac-sha512", 13, 32, 64 }
+	, { GETDNS_HMAC_MD5   , "hmac-md5"   ,  8
+	       , (uint8_t *)"\x08hmac-md5"   , 10, 10, 16 }
+};
+
+const getdns_tsig_info *_getdns_get_tsig_info(getdns_tsig_algo tsig_alg)
+{
+	return tsig_alg > sizeof(tsig_info) - 1
+	    || tsig_info[tsig_alg].alg == GETDNS_NO_TSIG ? NULL
+	    : &tsig_info[tsig_alg];
+}
+
+static const getdns_tsig_algo _getdns_get_tsig_algo(getdns_bindata *algo)
+{
+	getdns_tsig_info *i;
+
+	if (!algo || algo->size == 0)
+		return GETDNS_NO_TSIG;
+
+	if (algo->data[algo->size-1] != 0) {
+		/* Unterminated string */
+		for (i = tsig_info; i < tsig_info + sizeof(tsig_info); i++)
+			if (algo->size == i->strlen_name &&
+			    strncasecmp((const char *)algo->data, i->name,
+			    i->strlen_name) == 0)
+				return i->alg;
+		
+	} else if (!_getdns_bindata_is_dname(algo)) {
+		/* Terminated string */
+		for (i = tsig_info; i < tsig_info + sizeof(tsig_info); i++)
+			if (algo->size - 1 == i->strlen_name &&
+			    strncasecmp((const char *)algo->data, i->name,
+			    i->strlen_name) == 0)
+				return i->alg;
+
+	} else {
+		/* fqdn, canonical_dname_compare is now safe to use! */
+		for (i = tsig_info; i < tsig_info + sizeof(tsig_info); i++)
+			if (canonical_dname_compare(algo->data, i->dname) == 0)
+				return i->alg;
+	}
+	return GETDNS_NO_TSIG;
+}
+
 static void
 upstream_init(getdns_upstream *upstream,
     getdns_upstreams *parent, struct addrinfo *ai)
@@ -635,6 +694,10 @@ upstream_init(getdns_upstream *upstream,
 	upstream->has_prev_client_cookie = 0;
 	upstream->has_server_cookie = 0;
 
+	upstream->tsig_alg  = GETDNS_NO_TSIG;
+	upstream->tsig_dname_len = 0;
+	upstream->tsig_size = 0;
+
 	/* Tracking of network requests on this socket */
 	_getdns_rbtree_init(&upstream->netreq_by_query_id,
 	    net_req_query_id_cmp);
@@ -1715,15 +1778,21 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context,
 	upstreams = upstreams_create(
 	    context, count * GETDNS_UPSTREAM_TRANSPORTS);
 	for (i = 0; i < count; i++) {
-		getdns_dict *dict;
+		getdns_dict    *dict;
 		getdns_bindata *address_type;
 		getdns_bindata *address_data;
 		getdns_bindata *tls_auth_name;
 		struct sockaddr_storage  addr;
 
-		getdns_bindata *scope_id;
+		getdns_bindata  *scope_id;
 		getdns_upstream *upstream;
 
+		getdns_bindata  *tsig_alg_name, *tsig_name, *tsig_key;
+		getdns_tsig_algo tsig_alg;
+		char             tsig_name_str[1024];
+		uint8_t          tsig_dname_spc[256], *tsig_dname;
+		size_t           tsig_dname_len;
+
 		if ((r = getdns_list_get_dict(upstream_list, i, &dict)))
 			goto error;
 
@@ -1760,6 +1829,63 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context,
 			eos[scope_id->size] = 0;
 		}
 
+		tsig_alg_name = tsig_name = tsig_key = NULL;
+		tsig_dname = NULL;
+		tsig_dname_len = 0;
+
+		if (getdns_dict_get_bindata(dict,
+		    "tsig_algorithm", &tsig_alg_name) == GETDNS_RETURN_GOOD)
+			tsig_alg = _getdns_get_tsig_algo(tsig_alg_name);
+		else
+			tsig_alg = GETDNS_HMAC_MD5;
+
+		if (getdns_dict_get_bindata(dict, "tsig_name", &tsig_name))
+			tsig_alg = GETDNS_NO_TSIG; /* No name, no TSIG */
+
+		else if (tsig_name->size == 0)
+			tsig_alg = GETDNS_NO_TSIG;
+
+		else if (tsig_name->data[tsig_name->size - 1] != 0) {
+			/* Unterminated string */
+			if (tsig_name->size >= sizeof(tsig_name_str) - 1)
+				tsig_alg = GETDNS_NO_TSIG;
+			else {
+				(void) memcpy(tsig_name_str, tsig_name->data
+				                           , tsig_name->size);
+				tsig_name_str[tsig_name->size] = 0;
+
+				tsig_dname_len = sizeof(tsig_dname_spc);
+				if (gldns_str2wire_dname_buf(tsig_name_str,
+				    tsig_dname_spc, &tsig_dname_len))
+					tsig_alg = GETDNS_NO_TSIG;
+				else
+					tsig_dname = tsig_dname_spc;
+			}
+		} else if (!_getdns_bindata_is_dname(tsig_name)) {
+			/* Terminated string */
+			tsig_dname_len = sizeof(tsig_dname_spc);
+			if (gldns_str2wire_dname_buf(tsig_name_str,
+			    tsig_dname_spc, &tsig_dname_len))
+				tsig_alg = GETDNS_NO_TSIG;
+			else
+				tsig_dname = tsig_dname_spc;
+
+		} else if (tsig_name->size > sizeof(tsig_dname_spc))
+			tsig_alg = GETDNS_NO_TSIG;
+
+		else {
+			/* fqdn */
+			tsig_dname = memcpy(tsig_dname_spc, tsig_name->data
+			                                  , tsig_name->size);
+			tsig_dname_len = tsig_name->size;
+		}
+		if (getdns_dict_get_bindata(dict, "tsig_secret", &tsig_key))
+			tsig_alg = GETDNS_NO_TSIG; /* No key, no TSIG */
+
+		/* Don't check TSIG length contraints here.
+		 * Let the upstream decide what is secure enough.
+		 */
+
 		/* Loop to create upstreams as needed*/
 		for (size_t j = 0; j < GETDNS_UPSTREAM_TRANSPORTS; j++) {
 			uint32_t port;
@@ -1798,6 +1924,25 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context,
 					upstream->tls_auth_name[tls_auth_name->size] = '\0';
 				}
 			}
+			if ((upstream->tsig_alg = tsig_alg)) {
+				if (tsig_name) {
+					(void) memcpy(upstream->tsig_dname,
+					    tsig_dname, tsig_dname_len);
+					upstream->tsig_dname_len =
+						tsig_dname_len;
+				} else
+					upstream->tsig_dname_len = 0;
+
+				if (tsig_key) {
+					(void) memcpy(upstream->tsig_key,
+					    tsig_key->data, tsig_key->size);
+					upstream->tsig_size = tsig_key->size;
+				} else
+					upstream->tsig_size = 0;
+			} else {
+				upstream->tsig_dname_len = 0;
+				upstream->tsig_size = 0;
+			}
 			upstreams->count++;
 			freeaddrinfo(ai);
 		}
@@ -2542,9 +2687,12 @@ upstream_port(getdns_upstream *upstream)
 }
 
 static getdns_dict*
-_get_context_settings(getdns_context* context) {
+_get_context_settings(getdns_context* context)
+{
     getdns_return_t r = GETDNS_RETURN_GOOD;
     getdns_dict* result = getdns_dict_create_with_context(context);
+	getdns_list *upstreams;
+
     if (!result) {
         return NULL;
     }
@@ -2562,34 +2710,8 @@ _get_context_settings(getdns_context* context) {
     r |= getdns_dict_set_int(result, "append_name", context->append_name);
     /* list fields */
     if (context->suffix) r |= getdns_dict_set_list(result, "suffix", context->suffix);
-	if (context->upstreams && context->upstreams->count > 0) {
-		size_t i;
-		getdns_upstream *upstream;
-		getdns_list *upstreams =
-		    getdns_list_create_with_context(context);
-
-		for (i = 0; i < context->upstreams->count;) {
-			size_t j;
-			getdns_dict *d;
-			upstream = &context->upstreams->upstreams[i];
-			d = sockaddr_dict(context,
-			    (struct sockaddr *)&upstream->addr);
-			for ( j = 1, i++
-			    ; j < GETDNS_UPSTREAM_TRANSPORTS &&
-			      i < context->upstreams->count
-			    ; j++, i++) {
-
-				upstream = &context->upstreams->upstreams[i];
-				if (upstream->transport != GETDNS_TRANSPORT_TLS)
-					continue;
-				if (upstream_port(upstream) != getdns_port_array[j])
-					continue;
-				(void) getdns_dict_set_int(d, "tls_port",
-				    (uint32_t) upstream_port(upstream));
-			}
-			r |= _getdns_list_append_dict(upstreams, d);
-			getdns_dict_destroy(d);
-		}
+	
+	if (!getdns_context_get_upstream_recursive_servers(context, &upstreams)) {
 		r |= getdns_dict_set_list(result, "upstream_recursive_servers",
 		    upstreams);
 		getdns_list_destroy(upstreams);
@@ -2956,43 +3078,88 @@ getdns_context_get_dnssec_allowed_skew(getdns_context *context,
 
 getdns_return_t
 getdns_context_get_upstream_recursive_servers(getdns_context *context,
-    getdns_list **upstream_list) {
-    RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER);
-    RETURN_IF_NULL(upstream_list, GETDNS_RETURN_INVALID_PARAMETER);
-    *upstream_list = NULL;
-    if (context->upstreams && context->upstreams->count > 0) {
-        getdns_return_t r = GETDNS_RETURN_GOOD;
-        size_t i;
-        getdns_upstream *upstream;
-        getdns_list *upstreams = getdns_list_create();
-        for (i = 0; i < context->upstreams->count;) {
+    getdns_list **upstreams_r)
+{
+	size_t i;
+	getdns_list *upstreams;
+	getdns_return_t r;
+
+	if (!context || !upstreams_r)
+		return GETDNS_RETURN_INVALID_PARAMETER;
+
+	if (!(upstreams = getdns_list_create_with_context(context)))
+		return GETDNS_RETURN_MEMORY_ERROR;
+
+	if (!context->upstreams || context->upstreams->count == 0) {
+		*upstreams_r = upstreams;
+		return GETDNS_RETURN_GOOD;
+	}
+	r = GETDNS_RETURN_GOOD;
+	i = 0;
+	while (!r && i < context->upstreams->count) {
 		size_t j;
 		getdns_dict *d;
-		upstream = &context->upstreams->upstreams[i];
-		d = sockaddr_dict(context, (struct sockaddr *)&upstream->addr);
+		getdns_upstream  *upstream = &context->upstreams->upstreams[i];
+		getdns_bindata    bindata;
+		const getdns_tsig_info *tsig_info;
+
+		if (!(d =
+		    sockaddr_dict(context, (struct sockaddr*)&upstream->addr))) {
+			r = GETDNS_RETURN_MEMORY_ERROR;
+			break;
+		}
+		if (upstream->tsig_alg) {
+			tsig_info = _getdns_get_tsig_info(upstream->tsig_alg);
+
+			bindata.data = tsig_info->dname;
+			bindata.size = tsig_info->dname_len;
+			if ((r = getdns_dict_set_bindata(
+			    d, "tsig_algorithm", &bindata)))
+				break;
+
+			if (upstream->tsig_dname_len) {
+				bindata.data = upstream->tsig_dname;
+				bindata.size = upstream->tsig_dname_len;
+				if ((r = getdns_dict_set_bindata(
+				    d, "tsig_name", &bindata)))
+					break;
+			}
+			if (upstream->tsig_size) {
+				bindata.data = upstream->tsig_key;
+				bindata.size = upstream->tsig_size;
+				if ((r = getdns_dict_set_bindata(
+				    d, "tsig_secret", &bindata)))
+					break;
+			}
+		}
 		for ( j = 1, i++
 		    ; j < GETDNS_UPSTREAM_TRANSPORTS &&
 		      i < context->upstreams->count
 		    ; j++, i++) {
 
 			upstream = &context->upstreams->upstreams[i];
-			if (upstream->transport != GETDNS_TRANSPORT_TLS)
-				continue;
-			if (upstream_port(upstream) != getdns_port_array[j])
-				continue;
-			(void) getdns_dict_set_int(d, "tls_port",
-			    (uint32_t) upstream_port(upstream));
+
+			if (upstream->transport == GETDNS_TRANSPORT_UDP &&
+			    upstream_port(upstream) != getdns_port_array[j] &&
+			    (r = getdns_dict_set_int(d, "port",
+			    (uint32_t)upstream_port(upstream))))
+				break;
+
+			if (upstream->transport == GETDNS_TRANSPORT_TLS &&
+			    upstream_port(upstream) != getdns_port_array[j] &&
+			    (r = getdns_dict_set_int(d, "tls_port",
+			    (uint32_t)upstream_port(upstream))))
+				break;
 		}
-		r |= _getdns_list_append_dict(upstreams, d);
+		if (!r)
+			r = _getdns_list_append_dict(upstreams, d);
 		getdns_dict_destroy(d);
         }
-        if (r != GETDNS_RETURN_GOOD) {
-            getdns_list_destroy(upstreams);
-            return GETDNS_RETURN_MEMORY_ERROR;
-        }
-        *upstream_list = upstreams;
-    }
-    return GETDNS_RETURN_GOOD;
+        if (r)
+		getdns_list_destroy(upstreams);
+	else
+		*upstreams_r = upstreams;
+	return r;
 }
 
 getdns_return_t
diff --git a/src/context.h b/src/context.h
index 1e489d2d..9df5369a 100644
--- a/src/context.h
+++ b/src/context.h
@@ -79,6 +79,29 @@ typedef enum getdns_tls_hs_state {
 	GETDNS_HS_FAILED
 } getdns_tls_hs_state_t;
 
+typedef enum getdns_tsig_algo {
+	GETDNS_NO_TSIG     = 0, /* Do not use tsig */
+	GETDNS_HMAC_MD5    = 1, /* 128 bits */
+	GETDNS_GSS_TSIG    = 2, /* Not supported */
+	GETDNS_HMAC_SHA1   = 3, /* 160 bits */
+	GETDNS_HMAC_SHA224 = 4,
+	GETDNS_HMAC_SHA256 = 5,
+	GETDNS_HMAC_SHA384 = 6,
+	GETDNS_HMAC_SHA512 = 7
+} getdns_tsig_algo;
+
+typedef struct getdns_tsig_info {
+	getdns_tsig_algo  alg;
+	const char       *name;
+	size_t            strlen_name;
+	const uint8_t    *dname;
+	size_t            dname_len;
+	size_t            min_size; /* in # octets */
+	size_t            max_size; /* Actual size in # octets */
+} getdns_tsig_info;
+
+const getdns_tsig_info *_getdns_get_tsig_info(getdns_tsig_algo tsig_alg);
+
 typedef struct getdns_upstream {
 	/* backpointer to containing upstreams structure */
 	struct getdns_upstreams *upstreams;
@@ -120,6 +143,13 @@ typedef struct getdns_upstream {
 	unsigned has_server_cookie : 1;
 	unsigned server_cookie_len : 5;
 
+	/* TSIG */
+	uint8_t          tsig_dname[256];
+	size_t           tsig_dname_len;
+	size_t           tsig_size;
+	uint8_t          tsig_key[256];
+	getdns_tsig_algo tsig_alg;
+
 } getdns_upstream;
 
 typedef struct getdns_upstreams {
diff --git a/src/dict.c b/src/dict.c
index 822d5deb..5fa1c67c 100644
--- a/src/dict.c
+++ b/src/dict.c
@@ -656,12 +656,15 @@ getdns_indent(size_t indent)
 	return spaces + 80 - (indent < 80 ? indent : 0);
 }				/* getdns_indent */
 
-static int
+int
 _getdns_bindata_is_dname(getdns_bindata *bindata)
 {
 	size_t i = 0, n_labels = 0;
 
 	while (i < bindata->size && bindata->data[i]) {
+		if (bindata->data[i] & 0xC0) /* Compression pointer! */
+			return 0;
+
 		i += ((size_t)bindata->data[i]) + 1;
 		n_labels++;
 	}
diff --git a/src/dict.h b/src/dict.h
index c10a2bd8..90372dfb 100644
--- a/src/dict.h
+++ b/src/dict.h
@@ -71,6 +71,11 @@ getdns_return_t _getdns_dict_find(
 getdns_return_t _getdns_dict_find_and_add(
     getdns_dict *dict, const char *key, getdns_item **item);
 
+/* Return 1 (true) if bindata can be interpreted as an
+ * uncompressed dname.
+ */
+int _getdns_bindata_is_dname(getdns_bindata *bindata);
+
 #endif
 
 /* dict.h */