TOX-DNS V1 name resolution.

Added Tox DNS resolution via libldns. No DNSSEC yet. Only Tox DNS
 version 1 resolution.

 Added new development command /dns to test name resolution.
This commit is contained in:
Michael Raitza 2015-09-07 00:50:09 +02:00
parent 5e30b18d50
commit d5135c00bc
5 changed files with 587 additions and 79 deletions

View File

@ -26,11 +26,16 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})
find_package(WeeChat REQUIRED)
find_package(Tox REQUIRED
COMPONENTS CORE
OPTIONAL_COMPONENTS AV ENCRYPTSAVE)
OPTIONAL_COMPONENTS AV ENCRYPTSAVE DNS)
find_package(Ldns)
set(PLUGIN_PATH "lib/weechat/plugins" CACHE PATH
"Path to install the plugin binary to.")
if(Ldns_FOUND AND Tox_DNS_FOUND)
list(APPEND DNS_SRCS src/twc-dns.c)
endif()
add_library(tox MODULE
src/twc.c
src/twc-bootstrap.c
@ -38,6 +43,7 @@ add_library(tox MODULE
src/twc-commands.c
src/twc-completion.c
src/twc-config.c
${DNS_SRCS}
src/twc-friend-request.c
src/twc-gui.c
src/twc-group-invite.c
@ -53,8 +59,9 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall -Wextra -Wno-unused-paramet
include_directories(${Tox_INCLUDE_DIRS})
include_directories(${WeeChat_INCLUDE_DIRS})
include_directories(${Ldns_INCLUDE_DIRS})
target_link_libraries(tox ${Tox_LIBRARIES})
target_link_libraries(tox ${Tox_LIBRARIES} ${Ldns_LIBRARIES})
if(Tox_AV_FOUND)
add_definitions(-DTOXAV_ENABLED)
@ -64,8 +71,11 @@ if(Tox_ENCRYPTSAVE_FOUND)
add_definitions(-DTOXENCRYPTSAVE_ENABLED)
endif()
if(Ldns_FOUND AND Tox_DNS_FOUND)
add_definitions(-DLDNS_ENABLED)
endif()
# remove lib prefix (libtox.so -> tox.so)
set_target_properties(tox PROPERTIES PREFIX "")
install(TARGETS tox DESTINATION "${PLUGIN_PATH}")

14
cmake/FindLdns.cmake Normal file
View File

@ -0,0 +1,14 @@
set(Ldns_INCLUDE_DIRS)
set(Ldns_LIBRARIES)
find_path(Ldns_INCLUDE_DIR ldns/ldns.h)
find_library(Ldns_LIBRARY ldns)
if (Ldns_INCLUDE_DIR AND Ldns_LIBRARY)
set(Ldns_FOUND TRUE)
list(APPEND Ldns_LIBRARIES ldns)
list(APPEND Ldns_INCLUDE_DIRS ${Ldns_INCLUDE_DIR})
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Ldns FOUND_VAR Ldns_FOUND REQUIRED_VARS Ldns_INCLUDE_DIRS Ldns_LIBRARIES)

View File

@ -27,6 +27,7 @@
#include "twc-list.h"
#include "twc-profile.h"
#include "twc-chat.h"
#include "twc-dns.h"
#include "twc-friend-request.h"
#include "twc-group-invite.h"
#include "twc-bootstrap.h"
@ -41,6 +42,15 @@ enum TWC_FRIEND_MATCH
TWC_FRIEND_MATCH_NOMATCH = -2,
};
typedef struct
{
const bool force;
struct t_twc_profile *profile;
char *dns_name;
const char *message;
} t_twc_friend_add_data;
/**
* Make sure a command is executed on a Tox profile buffer. If not, warn user
* and abort.
@ -215,6 +225,165 @@ twc_cmd_bootstrap(void *data, struct t_gui_buffer *buffer,
return WEECHAT_RC_ERROR;
}
#ifdef LDNS_ENABLED
void
twc_cmd_dns_cb(void *data, enum t_twc_dns_rc rc, const uint8_t *tox_id)
{
if (!rc == TWC_DNS_RC_OK)
{
weechat_printf(weechat_current_buffer(), "%s Error resolving Tox DNS ID (%d).",
weechat_prefix("error"), rc);
return;
}
char toxid[TOX_ADDRESS_SIZE * 2 + 1];
twc_bin2hex(tox_id, TOX_ADDRESS_SIZE, toxid);
toxid[TOX_ADDRESS_SIZE * 2] = 0;
weechat_printf(weechat_current_buffer(), "Resolved Tox DNS ID to %s", toxid);
}
/**
* Command /dns callback.
*/
int
twc_cmd_dns(void *data, struct t_gui_buffer *buffer,
int argc, char **argv, char**argv_eol)
{
if (argc == 1)
return WEECHAT_RC_ERROR;
struct t_twc_profile *profile = twc_profile_search_buffer(buffer);
TWC_CHECK_PROFILE(profile);
TWC_CHECK_PROFILE_LOADED(profile);
char dns_id[TWC_DNS_ID_MAXLEN + 1];
snprintf(dns_id, TWC_DNS_ID_MAXLEN, "%s", argv_eol[1]);
enum t_twc_dns_rc dns_rc;
dns_rc = twc_dns_query(dns_id, &twc_cmd_dns_cb, NULL);
if (dns_rc)
{
weechat_printf(buffer, "%sResolving TOX-DNS ID. Error code %d",
weechat_prefix("error"), dns_rc);
return WEECHAT_RC_ERROR;
}
return WEECHAT_RC_OK;
}
#endif // LDNS_ENABLED
/**
* Sub-command '/friend add' callback.
*/
static void
twc_cmd_friend_add_cb(void *data, enum t_twc_dns_rc rc, const uint8_t *tox_id)
{
const t_twc_friend_add_data *d = data;
const bool force = d->force;
char *name = d->dns_name;
struct t_twc_profile *profile = d->profile;
const char *message = d->message;
char toxid[TOX_ADDRESS_SIZE * 2 + 1];
switch (rc)
{
case TWC_DNS_RC_OK:
break;
case TWC_DNS_RC_VERSION:
weechat_printf(profile->buffer,
"%sUnsupported Tox DNS version in reply.",
weechat_prefix("error"));
return;
case TWC_DNS_RC_ERROR:
weechat_printf(profile->buffer, "%sCould not resolve Tox ID.",
weechat_prefix("error"));
return;
case TWC_DNS_RC_EINVAL:
weechat_printf(profile->buffer, "%sInvalid Tox DNS ID.",
weechat_prefix("error"));
return;
default:
weechat_printf(profile->buffer,
"%sUnknown error resolving Tox ID (%d).",
weechat_prefix("error"), rc);
return;
}
twc_bin2hex(tox_id, TOX_ADDRESS_SIZE, toxid);
toxid[TOX_ADDRESS_SIZE * 2] = 0;
weechat_printf(profile->buffer, "Resolved tox dns id '%s' to %s", name, toxid);
free(name);
/* -force to delete friend before sending a new friend request */
if (force)
{
bool fail = false;
char *hex_key = strndup(toxid, TOX_PUBLIC_KEY_SIZE * 2);
int32_t friend_number = twc_match_friend(profile, hex_key);
free(hex_key);
if (friend_number == TWC_FRIEND_MATCH_AMBIGUOUS)
fail = true;
else if (friend_number != TWC_FRIEND_MATCH_NOMATCH)
fail = !tox_friend_delete(profile->tox, friend_number, NULL);
if (fail)
{
weechat_printf(profile->buffer,
"%scould not remove friend; please remove "
"manually before resending friend request",
weechat_prefix("error"));
return;
}
}
TOX_ERR_FRIEND_ADD err;
(void)tox_friend_add(profile->tox,
(uint8_t *)tox_id,
(uint8_t *)message,
strlen(message), &err);
switch (err)
{
case TOX_ERR_FRIEND_ADD_OK:
weechat_printf(profile->buffer,
"%sFriend request sent!",
weechat_prefix("network"));
break;
case TOX_ERR_FRIEND_ADD_TOO_LONG:
weechat_printf(profile->buffer,
"%sFriend request message too long! Try again.",
weechat_prefix("error"));
break;
case TOX_ERR_FRIEND_ADD_ALREADY_SENT:
case TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM:
weechat_printf(profile->buffer,
"%sYou have already sent a friend request to "
"that address (use -force to circumvent)",
weechat_prefix("error"));
break;
case TOX_ERR_FRIEND_ADD_OWN_KEY:
weechat_printf(profile->buffer,
"%sYou can't add yourself as a friend.",
weechat_prefix("error"));
break;
case TOX_ERR_FRIEND_ADD_BAD_CHECKSUM:
weechat_printf(profile->buffer,
"%sInvalid friend address - try again.",
weechat_prefix("error"));
break;
case TOX_ERR_FRIEND_ADD_MALLOC:
weechat_printf(profile->buffer,
"%sCould not add friend (out of memory).",
weechat_prefix("error"));
break;
case TOX_ERR_FRIEND_ADD_NULL:
case TOX_ERR_FRIEND_ADD_NO_MESSAGE: /* this should not happen as we
validate the message */
default:
weechat_printf(profile->buffer,
"%sCould not add friend (unknown error %d).",
weechat_prefix("error"), err);
break;
}
}
/**
* Command /friend callback.
*/
@ -286,89 +455,48 @@ twc_cmd_friend(void *data, struct t_gui_buffer *buffer,
if (!message)
message = weechat_config_string(twc_config_friend_request_message);
if (strlen(hex_id) != TOX_ADDRESS_SIZE * 2)
/* strip "tox:" URI prefix */
char *l = weechat_strcasestr(hex_id, "tox:");
if (l && (l == hex_id))
hex_id = &hex_id[4];
char *dns_name = strdup(hex_id);
if (!dns_name)
{
weechat_printf(profile->buffer,
"%sTox ID length invalid. Please try again.",
"%sMemory allocation error.",
weechat_prefix("error"));
return WEECHAT_RC_OK;
}
t_twc_friend_add_data data = { force, profile, dns_name, message };
uint8_t address[TOX_ADDRESS_SIZE];
if (strlen(hex_id) != TOX_ADDRESS_SIZE * 2)
{
#ifdef LDNS_ENABLED
/* resolve toxid */
enum t_twc_dns_rc dns_rc;
dns_rc = twc_dns_query(hex_id, &twc_cmd_friend_add_cb, &data);
if (dns_rc)
{
weechat_printf(profile->buffer,
"%sError calling Tox DNS resolver (%d).",
weechat_prefix("error"), dns_rc);
}
#else
/* immediately fail */
weechat_printf(profile->buffer,
"%sTox ID length invalid and Tox DNS resolution not implemented."
" Please try again.",
weechat_prefix("error"));
#endif // LDNS_ENABLED
return WEECHAT_RC_OK;
}
uint8_t address[TOX_ADDRESS_SIZE];
twc_hex2bin(hex_id, TOX_ADDRESS_SIZE, address);
/* call the callback by hand and finish the /friend add command */
twc_cmd_friend_add_cb(&data, TWC_DNS_RC_OK, address);
if (force)
{
bool fail = false;
char *hex_key = strndup(hex_id, TOX_PUBLIC_KEY_SIZE * 2);
int32_t friend_number = twc_match_friend(profile, hex_key);
free(hex_key);
if (friend_number == TWC_FRIEND_MATCH_AMBIGUOUS)
fail = true;
else if (friend_number != TWC_FRIEND_MATCH_NOMATCH)
fail = !tox_friend_delete(profile->tox, friend_number, NULL);
if (fail)
{
weechat_printf(profile->buffer,
"%scould not remove friend; please remove "
"manually before resending friend request",
weechat_prefix("error"));
return WEECHAT_RC_OK;
}
}
TOX_ERR_FRIEND_ADD err;
(void)tox_friend_add(profile->tox,
(uint8_t *)address,
(uint8_t *)message,
strlen(message), &err);
switch (err)
{
case TOX_ERR_FRIEND_ADD_OK:
weechat_printf(profile->buffer,
"%sFriend request sent!",
weechat_prefix("network"));
break;
case TOX_ERR_FRIEND_ADD_TOO_LONG:
weechat_printf(profile->buffer,
"%sFriend request message too long! Try again.",
weechat_prefix("error"));
break;
case TOX_ERR_FRIEND_ADD_ALREADY_SENT:
case TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM:
weechat_printf(profile->buffer,
"%sYou have already sent a friend request to "
"that address (use -force to circumvent)",
weechat_prefix("error"));
break;
case TOX_ERR_FRIEND_ADD_OWN_KEY:
weechat_printf(profile->buffer,
"%sYou can't add yourself as a friend.",
weechat_prefix("error"));
break;
case TOX_ERR_FRIEND_ADD_BAD_CHECKSUM:
weechat_printf(profile->buffer,
"%sInvalid friend address - try again.",
weechat_prefix("error"));
break;
case TOX_ERR_FRIEND_ADD_MALLOC:
weechat_printf(profile->buffer,
"%sCould not add friend (out of memory).",
weechat_prefix("error"));
break;
case TOX_ERR_FRIEND_ADD_NULL:
case TOX_ERR_FRIEND_ADD_NO_MESSAGE: /* this should not happen as we
validate the message */
default:
weechat_printf(profile->buffer,
"%sCould not add friend (unknown error %d).",
weechat_prefix("error"), err);
break;
}
return WEECHAT_RC_OK;
}
@ -668,6 +796,7 @@ twc_cmd_me(void *data, struct t_gui_buffer *buffer,
return WEECHAT_RC_OK;
}
/**
* Command /msg callback.
*/
@ -1208,6 +1337,13 @@ twc_commands_init()
"<message>",
"message: message to send",
NULL, twc_cmd_me, NULL);
#ifdef LDNS_ENABLED
weechat_hook_command("dns",
"test toxdns",
"<toxdnsid>",
"[tox:]<name>[@|._tox.]<domain>\n",
NULL, twc_cmd_dns, NULL);
#endif // LDNS_ENABLED
weechat_hook_command("msg",
"send a message to a Tox friend",

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2015 Håvard Pettersson <mail@haavard.me>
* Copyright (c) 2015 Håvard Pettersson <mail@haavard.me>,
* Michael Raitza <spacefrogg-devel@meterriblecrew.net>
*
* This file is part of Tox-WeeChat.
*
@ -21,10 +22,14 @@
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <weechat/weechat-plugin.h>
#include <tox/tox.h>
#include <ldns/ldns.h>
#undef dprintf /* well, yeah, well... */
#include "twc.h"
#include "twc-utils.h"
@ -43,6 +48,312 @@ struct t_twc_dns_callback_info
struct t_hook *hook;
};
/**
* Struct holding the split dns_id.
*/
typedef struct
{
char *name;
char *domain;
} t_twc_toxdns_data;
/**
* Struct holding a list of DNS responses.
*/
typedef struct
{
size_t count;
uint8_t **data;
} t_resolve_response_list;
static t_twc_toxdns_data *
toxdns_data_new(size_t namelen, size_t domlen)
{
t_twc_toxdns_data *d;
d = (t_twc_toxdns_data *)malloc(sizeof(t_twc_toxdns_data)
+ namelen + domlen + 2);
if (!d)
return NULL;
d->name = (char *)malloc(namelen + 1);
if (!d->name)
{
free(d);
return NULL;
}
d->domain = (char *)malloc(domlen + 1);
if (!d->domain)
{
free(d);
free(d->name);
return NULL;
}
return d;
}
static void
toxdns_data_deep_free(t_twc_toxdns_data *d)
{
if (d)
{
if (d->name)
free(d->name);
if (d->domain)
free(d->domain);
free(d);
}
}
/**
* Splits [tox:]<name>[@|._tox.]<domain> into a struct S with two
* fields S.name = "<name>" and S.domain = "_tox.<domain>.". Note the
* ending dot that finalises the DNS domain for later query!
*
* name: a string that is shorter than or equal to RESOLVE_ID_MAXLEN
* and must be in the form mentioned above.
*/
static t_twc_toxdns_data *
split_name(const char *name, enum t_twc_dns_rc *error)
{
enum t_twc_dns_rc err = TWC_DNS_RC_OK;
t_twc_toxdns_data *data = NULL;
uint32_t name_len = strlen(name);
char n[name_len + 1];
char d[name_len + 6];
char *domain;
char *d_pos = d;
char *n_pos = n;
strcpy(n, name);
domain = strstr(n, "tox:"); /* ignore a "tox:" uri prefix */
if (domain && (domain == n))
n_pos = &n[4];
domain = strstr(n_pos, "._tox.");
if ((!domain) || (domain == n_pos) || (strlen(domain) <= 6))
{
domain = strstr(n_pos, "@");
if ((!domain) || (domain == n_pos) || (strlen(domain) <= 1))
{
err = TWC_DNS_RC_EINVAL;
goto err;
}
d_pos = stpcpy(d_pos, "_tox.");
}
d_pos = stpcpy(d_pos, &domain[1]);
strcpy(d_pos, ".");
domain[0] = 0;
data = toxdns_data_new(strlen(n_pos), strlen(d));
if (!data)
{
err = TWC_DNS_RC_ERROR;
goto err;
}
strcpy(data->name, n_pos);
strcpy(data->domain, d);
err:
*error = err;
return data;
}
static size_t
resolve_response_count(const t_resolve_response_list *resp)
{
if (resp)
return resp->count;
return 0;
}
static uint8_t *
resolve_response_data(const t_resolve_response_list *resp, const size_t nr)
{
if (nr < resolve_response_count(resp))
return resp->data[nr];
return NULL;
}
static t_resolve_response_list *
resolve_response_list_new(size_t size)
{
t_resolve_response_list *resps;
resps = (t_resolve_response_list *)malloc(sizeof(t_resolve_response_list)
+ sizeof(char*) * size);
if (!resps)
return NULL;
resps->count = 0;
resps->data = (uint8_t **)&resps->data + 1;
return resps;
}
static void
resolve_response_list_deep_free(t_resolve_response_list *resp)
{
if (!resp)
return;
for (size_t i = 0; i < resolve_response_count(resp); ++i)
if (resp->data[i])
free(resp->data[i]);
free(resp);
}
static ldns_rr_list *
twc_ldns_query(ldns_resolver* res, const char *name, const ldns_rr_type t,
enum t_twc_dns_rc *err)
{
ldns_rdf *domain = ldns_dname_new_frm_str(name);
ldns_pkt *pkt;
ldns_rr_list *txt = NULL;
*err = TWC_DNS_RC_ERROR;
if (!domain)
goto err_dom;
for (size_t i = 0; i < ldns_resolver_nameserver_count(res); ++i)
{
pkt = ldns_resolver_query(res, domain, t, LDNS_RR_CLASS_IN, LDNS_RD);
if (!pkt)
goto fin;
switch (ldns_pkt_get_rcode(pkt))
{
case LDNS_RCODE_NOERROR:
*err = TWC_DNS_RC_OK;
txt = ldns_pkt_rr_list_by_type(pkt, t, LDNS_SECTION_ANSWER);
goto fin;
case LDNS_RCODE_REFUSED:
case LDNS_RCODE_NXDOMAIN:
case LDNS_RCODE_SERVFAIL:
for (size_t i = 0; i < ldns_resolver_nameserver_count(res); ++i)
if (!ldns_rdf_compare(ldns_pkt_answerfrom(pkt),
ldns_resolver_nameservers(res)[i]))
{
/* mark faulty nameserver as unreachable */
ldns_resolver_set_nameserver_rtt(res, i, LDNS_RESOLV_RTT_INF);
break;
}
break;
default:
goto fin;
}
}
fin:
ldns_rdf_deep_free(domain);
if (pkt)
ldns_pkt_free(pkt);
err_dom:
return txt;
}
/**
* Resolve the TXT records for NAME and return all responses. Outer
* quotes are stripped from TXT records. All fields of a TXT record
* are stiched together to a long string stripping white space as
* requested by protocol.
*/
static t_resolve_response_list *
twc_ldns_resolve(char *name, enum t_twc_dns_rc *err) {
ldns_resolver *res;
ldns_status s;
ldns_rr_list *txt = NULL;
t_resolve_response_list *txts = NULL;
*err= TWC_DNS_RC_OK;
s = ldns_resolver_new_frm_file(&res, NULL);
if (s != LDNS_STATUS_OK)
{
*err = TWC_DNS_RC_ERROR;
goto err;
}
txt = twc_ldns_query(res, name, LDNS_RR_TYPE_TXT, err);
if (!txt)
goto err_pkt;
txts = resolve_response_list_new(ldns_rr_list_rr_count(txt));
if (!txts)
{
*err = TWC_DNS_RC_ERROR;
goto err_txt;
}
for (size_t i = 0; i < ldns_rr_list_rr_count(txt); ++i)
{
ldns_rr *rr = ldns_rr_list_rr(txt, i);
char txt_field[256] = {0}; /* TXT RR RDATA can be 255 bytes long */
char *cur_pos = txt_field;
/* stich together all TXT RR RDATA fields, stripping quotes */
for (size_t j = 0; j < ldns_rr_rd_count(rr); ++j)
{
char *rdata = ldns_rdf2str(ldns_rr_rdf(rr, j));
size_t len = strlen(rdata);
if (rdata[0] == '"')
{
rdata[len - 1] = 0;
cur_pos = stpcpy(cur_pos, &rdata[1]);
} else
cur_pos = stpcpy(cur_pos, rdata);
free(rdata);
}
txts->data[i] = (uint8_t *)strdup(txt_field);
if (!txts->data[i])
{
*err = TWC_DNS_RC_ERROR;
goto err_resp;
}
++txts->count;
}
goto err_txt; // Normal return
err_resp:
resolve_response_list_deep_free(txts);
txts = NULL;
err_txt:
ldns_rr_list_deep_free(txt);
err_pkt:
ldns_resolver_deep_free(res);
err:
return txts;
}
static uint8_t *
resolve_toxdns1(const t_twc_toxdns_data *data, enum t_twc_dns_rc *error)
{
char buf[TWC_DNS_ID_MAXLEN + 2];
t_resolve_response_list *id_rrs;
uint8_t *toxid = NULL;
*error = TWC_DNS_RC_OK;
sprintf(buf, "%s.%s", data->name, data->domain);
id_rrs = twc_ldns_resolve(buf, error);
if (!id_rrs)
goto err;
for (size_t j = 0; j < id_rrs->count; ++j)
{
uint8_t *id_rr = resolve_response_data(id_rrs, j);
if (strncmp("v=tox1;id=", (char *)id_rr, 10))
{
*error = TWC_DNS_RC_VERSION;
continue;
}
toxid = (uint8_t *)strndup((char *)&id_rr[10], TOX_ADDRESS_SIZE * 2);
if (!toxid)
{
*error = TWC_DNS_RC_ERROR;
goto err_id;
}
*error = TWC_DNS_RC_OK;
break;
}
err_id:
resolve_response_list_deep_free(id_rrs);
err:
return toxid;
}
/**
* Callback when the DNS resolver child process has written to our file
* descriptor. Process the data a bit and pass it on to the original callback.
@ -87,7 +398,35 @@ twc_dns_fd_callback(void *data, int fd)
void
twc_perform_dns_lookup(const char *dns_id, int out_fd)
{
dprintf(out_fd, "%d", TWC_DNS_RC_ERROR);
enum t_twc_dns_rc err = TWC_DNS_RC_OK;
t_twc_toxdns_data *d;
uint8_t *toxid;
if (!dns_id || weechat_strlen_screen(dns_id) > TWC_DNS_ID_MAXLEN)
{
err = TWC_DNS_RC_EINVAL;
goto err;
}
/* Split name from domain in preparation for tox3dns protocol. */
d = split_name(dns_id, &err);
if (!d)
{
err = TWC_DNS_RC_EINVAL;
goto err;
}
/* For now only resolve Tox DNS version 1 addresses */
toxid = resolve_toxdns1(d, &err);
if (!toxid)
goto err;
err:
if (err)
dprintf(out_fd, "%d", err);
else
dprintf(out_fd, "%s", toxid);
toxdns_data_deep_free(d);
}
/**

View File

@ -22,10 +22,19 @@
#include <stdint.h>
#ifdef LDNS_ENABLED
#include <tox/toxdns.h>
#define TWC_DNS_ID_MAXLEN TOXDNS_MAX_RECOMMENDED_NAME_LENGTH
#endif // LDNS_ENABLED
enum t_twc_dns_rc
{
TWC_DNS_RC_OK = 0,
TWC_DNS_RC_ERROR = -1,
TWC_DNS_RC_EINVAL = -2,
TWC_DNS_RC_VERSION = -4,
};
enum t_twc_dns_rc