Add support for MX, TXT, NS and SOA records to GResolver

* Add resolver functions for looking up DNS records of
   various types. Currently implemented: MX, TXT, SOA, SRV, NS
 * Return records as GVariant tuples.
 * Make the GSrvTarget lookups a wrapper over this new
   functionality.
 * Rework the resolver test so that it has support for
   looking up MX, NS, SOA, TXT records, and uses GOptionContext

https://bugzilla.gnome.org/show_bug.cgi?id=672944
This commit is contained in:
Stef Walter 2012-04-04 17:13:10 +02:00
parent cec17df598
commit 666374c16f
8 changed files with 862 additions and 102 deletions

View File

@ -1699,6 +1699,9 @@ g_resolver_lookup_service
g_resolver_lookup_service_async
g_resolver_lookup_service_finish
g_resolver_free_targets
g_resolver_lookup_records
g_resolver_lookup_records_async
g_resolver_lookup_records_finish
<SUBSECTION>
G_RESOLVER_ERROR
@ -1717,6 +1720,7 @@ G_TYPE_RESOLVER
GResolverPrivate
g_resolver_get_type
g_resolver_error_quark
g_resolver_record_type_get_type
</SECTION>
<SECTION>

View File

@ -1713,3 +1713,7 @@ g_resources_unregister
g_static_resource_fini
g_static_resource_get_resource
g_static_resource_init
g_resolver_lookup_records
g_resolver_lookup_records_async
g_resolver_lookup_records_finish
g_resolver_record_type_get_type

View File

@ -630,6 +630,49 @@ typedef enum {
G_RESOLVER_ERROR_INTERNAL
} GResolverError;
/**
* GResolverRecordType:
* @G_RESOLVER_RECORD_SRV: lookup DNS SRV records for a domain
* @G_RESOLVER_RECORD_MX: lookup DNS MX records for a domain
* @G_RESOLVER_RECORD_TXT: lookup DNS TXT records for a name
* @G_RESOLVER_RECORD_SOA: lookup DNS SOA records for a zone
* @G_RESOLVER_RECORD_NS: lookup DNS NS records for a domain
*
* The type of record that g_resolver_lookup_records() or
* g_resolver_lookup_records_async() should retrieve. The records are returned
* as lists of #GVariant tuples. Each record type has different values in
* the variant tuples returned.
*
* %G_RESOLVER_RECORD_SRV records are returned as variants with the signature
* '(qqqs)', containing a guint16 with the priority, a guint16 with the
* weight, a guint16 with the port, and a string of the hostname.
*
* %G_RESOLVER_RECORD_MX records are returned as variants with the signature
* '(qs)', representing a guint16 with the preference, and a string containing
* the mail exchanger hostname.
*
* %G_RESOLVER_RECORD_TXT records are returned as variants with the signature
* '(as)', representing an array of the strings in the text record.
*
* %G_RESOLVER_RECORD_SOA records are returned as variants with the signature
* '(ssuuuuu)', representing a string containing the primary name server, a
* string containing the administrator, the serial as a guint32, the refresh
* interval as guint32, the retry interval as a guint32, the expire timeout
* as a guint32, and the ttl as a guint32.
*
* %G_RESOLVER_RECORD_NS records are returned as variants with the signature
* '(s)', representing a string of the hostname of the name server.
*
* Since: 2.34
*/
typedef enum {
G_RESOLVER_RECORD_SRV = 1,
G_RESOLVER_RECORD_MX,
G_RESOLVER_RECORD_TXT,
G_RESOLVER_RECORD_SOA,
G_RESOLVER_RECORD_NS
} GResolverRecordType;
/**
* GResourceError:
* @G_RESOURCE_ERROR_NOT_FOUND: no file was found at the requested path

View File

@ -95,13 +95,19 @@ char *_g_resolver_name_from_nameinfo (GInetAddress *address,
GError **error);
#if defined(G_OS_UNIX)
GList *_g_resolver_targets_from_res_query (const gchar *rrname,
gint _g_resolver_record_type_to_rrtype (GResolverRecordType record_type);
GList *_g_resolver_records_from_res_query (const gchar *rrname,
gint rrtype,
guchar *answer,
gint len,
gint herr,
GError **error);
#elif defined(G_OS_WIN32)
GList *_g_resolver_targets_from_DnsQuery (const gchar *rrname,
WORD _g_resolver_record_type_to_dnstype (GResolverRecordType record_type);
GList *_g_resolver_records_from_DnsQuery (const gchar *rrname,
WORD dnstype,
DNS_STATUS status,
DNS_RECORD *results,
GError **error);

View File

@ -78,11 +78,81 @@ struct _GResolverPrivate {
*/
G_DEFINE_TYPE (GResolver, g_resolver, G_TYPE_OBJECT)
static GList *
srv_records_to_targets (GList *records)
{
const gchar *hostname;
guint16 port, priority, weight;
GSrvTarget *target;
GList *l;
for (l = records; l != NULL; l = g_list_next (l))
{
g_variant_get (l->data, "(qqq&s)", &priority, &weight, &port, &hostname);
target = g_srv_target_new (hostname, port, priority, weight);
g_variant_unref (l->data);
l->data = target;
}
return g_srv_target_list_sort (records);
}
static GList *
g_resolver_real_lookup_service (GResolver *resolver,
const gchar *rrname,
GCancellable *cancellable,
GError **error)
{
GList *records;
records = G_RESOLVER_GET_CLASS (resolver)->lookup_records (resolver,
rrname,
G_RESOLVER_RECORD_SRV,
cancellable,
error);
return srv_records_to_targets (records);
}
static void
g_resolver_real_lookup_service_async (GResolver *resolver,
const gchar *rrname,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
G_RESOLVER_GET_CLASS (resolver)->lookup_records_async (resolver,
rrname,
G_RESOLVER_RECORD_SRV,
cancellable,
callback,
user_data);
}
static GList *
g_resolver_real_lookup_service_finish (GResolver *resolver,
GAsyncResult *result,
GError **error)
{
GList *records;
records = G_RESOLVER_GET_CLASS (resolver)->lookup_records_finish (resolver,
result,
error);
return srv_records_to_targets (records);
}
static void
g_resolver_class_init (GResolverClass *resolver_class)
{
volatile GType type;
/* Automatically pass these over to the lookup_records methods */
resolver_class->lookup_service = g_resolver_real_lookup_service;
resolver_class->lookup_service_async = g_resolver_real_lookup_service_async;
resolver_class->lookup_service_finish = g_resolver_real_lookup_service_finish;
g_type_class_add_private (resolver_class, sizeof (GResolverPrivate));
/* Make sure _g_networking_init() has been called */
@ -707,6 +777,112 @@ g_resolver_free_targets (GList *targets)
g_list_free (targets);
}
/**
* g_resolver_lookup_records:
* @resolver: a #GResolver
* @rrname: the DNS name to lookup the record for
* @record_type: the type of DNS record to lookup
* @cancellable: (allow-none): a #GCancellable, or %NULL
* @error: return location for a #GError, or %NULL
*
* Synchronously performs a DNS record lookup for the given @rrname and returns
* a list of records as #GVariant tuples. See #GResolverRecordType for
* information on what the records contain for each @record_type.
*
* If the DNS resolution fails, @error (if non-%NULL) will be set to
* a value from #GResolverError.
*
* If @cancellable is non-%NULL, it can be used to cancel the
* operation, in which case @error (if non-%NULL) will be set to
* %G_IO_ERROR_CANCELLED.
*
* Return value: (element-type GVariant) (transfer full): a #GList of #GVariant,
* or %NULL on error. You must free each of the records and the list when you are
* done with it. (You can use g_list_free_full() with g_variant_unref() to do this.)
*
* Since: 2.34
*/
GList *
g_resolver_lookup_records (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GError **error)
{
GList *records;
g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL);
g_return_val_if_fail (rrname != NULL, NULL);
g_resolver_maybe_reload (resolver);
records = G_RESOLVER_GET_CLASS (resolver)->
lookup_records (resolver, rrname, record_type, cancellable, error);
return records;
}
/**
* g_resolver_lookup_records_async:
* @resolver: a #GResolver
* @rrname: the DNS name to lookup the record for
* @record_type: the type of DNS record to lookup
* @cancellable: (allow-none): a #GCancellable, or %NULL
* @callback: (scope async): callback to call after resolution completes
* @user_data: (closure): data for @callback
*
* Begins asynchronously performing a DNS lookup for the given
* @rrname, and eventually calls @callback, which must call
* g_resolver_lookup_records_finish() to get the final result. See
* g_resolver_lookup_records() for more details.
*
* Since: 2.34
*/
void
g_resolver_lookup_records_async (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_if_fail (G_IS_RESOLVER (resolver));
g_return_if_fail (rrname != NULL);
g_resolver_maybe_reload (resolver);
G_RESOLVER_GET_CLASS (resolver)->
lookup_records_async (resolver, rrname, record_type, cancellable, callback, user_data);
}
/**
* g_resolver_lookup_records_finish:
* @resolver: a #GResolver
* @result: the result passed to your #GAsyncReadyCallback
* @error: return location for a #GError, or %NULL
*
* Retrieves the result of a previous call to
* g_resolver_lookup_records_async(). Returns a list of records as #GVariant
* tuples. See #GResolverRecordType for information on what the records contain.
*
* If the DNS resolution failed, @error (if non-%NULL) will be set to
* a value from #GResolverError. If the operation was cancelled,
* @error will be set to %G_IO_ERROR_CANCELLED.
*
* Return value: (element-type GVariant) (transfer full): a #GList of #GVariant,
* or %NULL on error. You must free each of the records and the list when you are
* done with it. (You can use g_list_free_full() with g_variant_unref() to do this.)
*
* Since: 2.34
*/
GList *
g_resolver_lookup_records_finish (GResolver *resolver,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL);
return G_RESOLVER_GET_CLASS (resolver)->
lookup_records_finish (resolver, result, error);
}
/**
* g_resolver_error_quark:
*
@ -821,9 +997,133 @@ _g_resolver_name_from_nameinfo (GInetAddress *address,
}
#if defined(G_OS_UNIX)
static GVariant *
parse_res_srv (guchar *answer,
guchar *end,
guchar **p)
{
gchar namebuf[1024];
guint16 priority, weight, port;
GETSHORT (priority, *p);
GETSHORT (weight, *p);
GETSHORT (port, *p);
*p += dn_expand (answer, end, *p, namebuf, sizeof (namebuf));
return g_variant_new ("(qqqs)",
priority,
weight,
port,
namebuf);
}
static GVariant *
parse_res_soa (guchar *answer,
guchar *end,
guchar **p)
{
gchar mnamebuf[1024];
gchar rnamebuf[1024];
guint32 serial, refresh, retry, expire, ttl;
*p += dn_expand (answer, end, *p, mnamebuf, sizeof (mnamebuf));
*p += dn_expand (answer, end, *p, rnamebuf, sizeof (rnamebuf));
GETLONG (serial, *p);
GETLONG (refresh, *p);
GETLONG (retry, *p);
GETLONG (expire, *p);
GETLONG (ttl, *p);
return g_variant_new ("(ssuuuuu)",
mnamebuf,
rnamebuf,
serial,
refresh,
retry,
expire,
ttl);
}
static GVariant *
parse_res_ns (guchar *answer,
guchar *end,
guchar **p)
{
gchar namebuf[1024];
*p += dn_expand (answer, end, *p, namebuf, sizeof (namebuf));
return g_variant_new ("(s)", namebuf);
}
static GVariant *
parse_res_mx (guchar *answer,
guchar *end,
guchar **p)
{
gchar namebuf[1024];
guint16 preference;
GETSHORT (preference, *p);
*p += dn_expand (answer, end, *p, namebuf, sizeof (namebuf));
return g_variant_new ("(qs)",
preference,
namebuf);
}
static GVariant *
parse_res_txt (guchar *answer,
guchar *end,
guchar **p)
{
GVariant *record;
GPtrArray *array;
guchar *at = *p;
gsize len;
array = g_ptr_array_new_with_free_func (g_free);
while (at < end)
{
len = *(at++);
if (len > at - end)
break;
g_ptr_array_add (array, g_strndup ((gchar *)at, len));
at += len;
}
*p = at;
record = g_variant_new ("(@as)",
g_variant_new_strv ((const gchar **)array->pdata, array->len));
g_ptr_array_free (array, TRUE);
return record;
}
gint
_g_resolver_record_type_to_rrtype (GResolverRecordType type)
{
switch (type)
{
case G_RESOLVER_RECORD_SRV:
return T_SRV;
case G_RESOLVER_RECORD_TXT:
return T_TXT;
case G_RESOLVER_RECORD_SOA:
return T_SOA;
case G_RESOLVER_RECORD_NS:
return T_NS;
case G_RESOLVER_RECORD_MX:
return T_MX;
}
g_return_val_if_reached (-1);
}
/* Private method to process a res_query response into GSrvTargets */
GList *
_g_resolver_targets_from_res_query (const gchar *rrname,
_g_resolver_records_from_res_query (const gchar *rrname,
gint rrtype,
guchar *answer,
gint len,
gint herr,
@ -832,11 +1132,11 @@ _g_resolver_targets_from_res_query (const gchar *rrname,
gint count;
gchar namebuf[1024];
guchar *end, *p;
guint16 type, qclass, rdlength, priority, weight, port;
guint16 type, qclass, rdlength;
guint32 ttl;
HEADER *header;
GSrvTarget *target;
GList *targets;
GList *records;
GVariant *record;
if (len <= 0)
{
@ -846,7 +1146,7 @@ _g_resolver_targets_from_res_query (const gchar *rrname,
if (len == 0 || herr == HOST_NOT_FOUND || herr == NO_DATA)
{
errnum = G_RESOLVER_ERROR_NOT_FOUND;
format = _("No service record for '%s'");
format = _("No DNS record of the requested type for '%s'");
}
else if (herr == TRY_AGAIN)
{
@ -863,7 +1163,7 @@ _g_resolver_targets_from_res_query (const gchar *rrname,
return NULL;
}
targets = NULL;
records = NULL;
header = (HEADER *)answer;
p = answer + sizeof (HEADER);
@ -875,6 +1175,9 @@ _g_resolver_targets_from_res_query (const gchar *rrname,
{
p += dn_expand (answer, end, p, namebuf, sizeof (namebuf));
p += 4;
/* To silence gcc warnings */
namebuf[0] = namebuf[1];
}
/* Read answers */
@ -888,34 +1191,126 @@ _g_resolver_targets_from_res_query (const gchar *rrname,
ttl = ttl; /* To avoid -Wunused-but-set-variable */
GETSHORT (rdlength, p);
if (type != T_SRV || qclass != C_IN)
if (type != rrtype || qclass != C_IN)
{
p += rdlength;
continue;
}
GETSHORT (priority, p);
GETSHORT (weight, p);
GETSHORT (port, p);
p += dn_expand (answer, end, p, namebuf, sizeof (namebuf));
switch (rrtype)
{
case T_SRV:
record = parse_res_srv (answer, end, &p);
break;
case T_MX:
record = parse_res_mx (answer, end, &p);
break;
case T_SOA:
record = parse_res_soa (answer, end, &p);
break;
case T_NS:
record = parse_res_ns (answer, end, &p);
break;
case T_TXT:
record = parse_res_txt (answer, p + rdlength, &p);
break;
default:
g_warn_if_reached ();
record = NULL;
break;
}
target = g_srv_target_new (namebuf, port, priority, weight);
targets = g_list_prepend (targets, target);
if (record != NULL)
records = g_list_prepend (records, record);
}
return g_srv_target_list_sort (targets);
return records;
}
#elif defined(G_OS_WIN32)
/* Private method to process a DnsQuery response into GSrvTargets */
static GVariant *
parse_dns_srv (DNS_RECORD *rec)
{
return g_variant_new ("(qqqs)",
(guint16)rec->Data.SRV.wPriority,
(guint16)rec->Data.SRV.wWeight,
(guint16)rec->Data.SRV.wPort,
rec->Data.SRV.pNameTarget);
}
static GVariant *
parse_dns_soa (DNS_RECORD *rec)
{
return g_variant_new ("(ssuuuuu)",
rec->Data.SOA.pNamePrimaryServer,
rec->Data.SOA.pNameAdministrator,
(guint32)rec->Data.SOA.dwSerialNo,
(guint32)rec->Data.SOA.dwRefresh,
(guint32)rec->Data.SOA.dwRetry,
(guint32)rec->Data.SOA.dwExpire,
(guint32)rec->Data.SOA.dwDefaultTtl);
}
static GVariant *
parse_dns_ns (DNS_RECORD *rec)
{
return g_variant_new ("(s)", rec->Data.NS.pNameHost);
}
static GVariant *
parse_dns_mx (DNS_RECORD *rec)
{
return g_variant_new ("(qs)",
(guint16)rec->Data.MX.wPreference,
rec->Data.MX.pNameExchange);
}
static GVariant *
parse_dns_txt (DNS_RECORD *rec)
{
GVariant *record;
GPtrArray *array;
DWORD i;
array = g_ptr_array_new ();
for (i = 0; i < rec->Data.TXT.dwStringCount; i++)
g_ptr_array_add (array, rec->Data.TXT.pStringArray[i]);
record = g_variant_new ("(@as)",
g_variant_new_strv ((const gchar **)array->pdata, array->len));
g_ptr_array_free (array, TRUE);
return record;
}
WORD
_g_resolver_record_type_to_dnstype (GResolverRecordType type)
{
switch (type)
{
case G_RESOLVER_RECORD_SRV:
return DNS_TYPE_SRV;
case G_RESOLVER_RECORD_TXT:
return DNS_TYPE_TEXT;
case G_RESOLVER_RECORD_SOA:
return DNS_TYPE_SOA;
case G_RESOLVER_RECORD_NS:
return DNS_TYPE_NS;
case G_RESOLVER_RECORD_MX:
return DNS_TYPE_MX;
}
g_return_val_if_reached (-1);
}
/* Private method to process a DnsQuery response into GVariants */
GList *
_g_resolver_targets_from_DnsQuery (const gchar *rrname,
_g_resolver_records_from_DnsQuery (const gchar *rrname,
WORD dnstype,
DNS_STATUS status,
DNS_RECORD *results,
GError **error)
{
DNS_RECORD *rec;
GSrvTarget *target;
GList *targets;
gpointer record;
GList *records;
if (status != ERROR_SUCCESS)
{
@ -925,7 +1320,7 @@ _g_resolver_targets_from_DnsQuery (const gchar *rrname,
if (status == DNS_ERROR_RCODE_NAME_ERROR)
{
errnum = G_RESOLVER_ERROR_NOT_FOUND;
format = _("No service record for '%s'");
format = _("No DNS record of the requested type for '%s'");
}
else if (status == DNS_ERROR_RCODE_SERVER_FAILURE)
{
@ -942,20 +1337,38 @@ _g_resolver_targets_from_DnsQuery (const gchar *rrname,
return NULL;
}
targets = NULL;
records = NULL;
for (rec = results; rec; rec = rec->pNext)
{
if (rec->wType != DNS_TYPE_SRV)
if (rec->wType != dnstype)
continue;
target = g_srv_target_new (rec->Data.SRV.pNameTarget,
rec->Data.SRV.wPort,
rec->Data.SRV.wPriority,
rec->Data.SRV.wWeight);
targets = g_list_prepend (targets, target);
switch (dnstype)
{
case DNS_TYPE_SRV:
record = parse_dns_srv (rec);
break;
case DNS_TYPE_SOA:
record = parse_dns_soa (rec);
break;
case DNS_TYPE_NS:
record = parse_dns_ns (rec);
break;
case DNS_TYPE_MX:
record = parse_dns_mx (rec);
break;
case DNS_TYPE_TEXT:
record = parse_dns_txt (rec);
break;
default:
g_warn_if_reached ();
record = NULL;
break;
}
if (record != NULL)
records = g_list_prepend (records, g_variant_ref_sink (record));
}
return g_srv_target_list_sort (targets);
return records;
}
#endif

View File

@ -91,10 +91,24 @@ struct _GResolverClass {
GAsyncResult *result,
GError **error);
GList * ( *lookup_records) (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GError **error);
void ( *lookup_records_async) (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GList * ( *lookup_records_finish) (GResolver *resolver,
GAsyncResult *result,
GError **error);
/* Padding for future expansion */
void (*_g_reserved1) (void);
void (*_g_reserved2) (void);
void (*_g_reserved3) (void);
void (*_g_reserved4) (void);
void (*_g_reserved5) (void);
void (*_g_reserved6) (void);
@ -150,6 +164,21 @@ GList *g_resolver_lookup_service_finish (GResolver *resolver,
GAsyncResult *result,
GError **error);
GList *g_resolver_lookup_records (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GError **error);
void g_resolver_lookup_records_async (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GList *g_resolver_lookup_records_finish (GResolver *resolver,
GAsyncResult *result,
GError **error);
void g_resolver_free_targets (GList *targets);
/**

View File

@ -151,8 +151,9 @@ struct _GThreadedResolverRequest {
} address;
struct {
gchar *rrname;
GList *targets;
} service;
GResolverRecordType record_type;
GList *results;
} records;
} u;
GCancellable *cancellable;
@ -515,85 +516,108 @@ lookup_by_address_finish (GResolver *resolver,
static void
do_lookup_service (GThreadedResolverRequest *req,
GError **error)
do_lookup_records (GThreadedResolverRequest *req,
GError **error)
{
#if defined(G_OS_UNIX)
gint len, herr;
guchar answer[1024];
gint len = 512;
gint herr;
GByteArray *answer;
gint rrtype;
rrtype = _g_resolver_record_type_to_rrtype (req->u.records.record_type);
answer = g_byte_array_new ();
for (;;)
{
g_byte_array_set_size (answer, len * 2);
len = res_query (req->u.records.rrname, C_IN, rrtype, answer->data, answer->len);
/* If answer fit in the buffer then we're done */
if (len < 0 || len < (gint)answer->len)
break;
/*
* On overflow some res_query's return the length needed, others
* return the full length entered. This code works in either case.
*/
}
herr = h_errno;
req->u.records.results = _g_resolver_records_from_res_query (req->u.records.rrname, rrtype, answer->data, len, herr, error);
g_byte_array_free (answer, TRUE);
#elif defined(G_OS_WIN32)
DNS_STATUS status;
DNS_RECORD *results;
#endif
DNS_RECORD *results = NULL;
WORD dnstype;
#if defined(G_OS_UNIX)
len = res_query (req->u.service.rrname, C_IN, T_SRV, answer, sizeof (answer));
herr = h_errno;
req->u.service.targets = _g_resolver_targets_from_res_query (req->u.service.rrname, answer, len, herr, error);
#elif defined(G_OS_WIN32)
status = DnsQuery_A (req->u.service.rrname, DNS_TYPE_SRV,
DNS_QUERY_STANDARD, NULL, &results, NULL);
req->u.service.targets = _g_resolver_targets_from_DnsQuery (req->u.service.rrname, status, results, error);
DnsRecordListFree (results, DnsFreeRecordList);
dnstype = _g_resolver_record_type_to_dnstype (req->u.records.record_type);
status = DnsQuery_A (req->u.records.rrname, dnstype, DNS_QUERY_STANDARD, NULL, &results, NULL);
req->u.records.results = _g_resolver_records_from_DnsQuery (req->u.records.rrname, dnstype, status, results, error);
if (results != NULL)
DnsRecordListFree (results, DnsFreeRecordList);
#endif
}
static GList *
lookup_service (GResolver *resolver,
const gchar *rrname,
GCancellable *cancellable,
GError **error)
lookup_records (GResolver *resolver,
const gchar *rrname,
GResolverRecordType record_type,
GCancellable *cancellable,
GError **error)
{
GThreadedResolver *gtr = G_THREADED_RESOLVER (resolver);
GThreadedResolverRequest *req;
GList *targets;
GList *results;
req = g_threaded_resolver_request_new (do_lookup_service, NULL, cancellable);
req->u.service.rrname = (char *)rrname;
req = g_threaded_resolver_request_new (do_lookup_records, NULL, cancellable);
req->u.records.rrname = (char *)rrname;
req->u.records.record_type = record_type;
resolve_sync (gtr, req, error);
targets = req->u.service.targets;
results = req->u.records.results;
g_threaded_resolver_request_unref (req);
return targets;
return results;
}
static void
free_lookup_service (GThreadedResolverRequest *req)
free_lookup_records (GThreadedResolverRequest *req)
{
g_free (req->u.service.rrname);
if (req->u.service.targets)
g_resolver_free_targets (req->u.service.targets);
g_free (req->u.records.rrname);
g_list_free_full (req->u.records.results, (GDestroyNotify)g_variant_unref);
}
static void
lookup_service_async (GResolver *resolver,
lookup_records_async (GResolver *resolver,
const char *rrname,
GCancellable *cancellable,
GResolverRecordType record_type,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
gpointer user_data)
{
GThreadedResolver *gtr = G_THREADED_RESOLVER (resolver);
GThreadedResolverRequest *req;
req = g_threaded_resolver_request_new (do_lookup_service,
free_lookup_service,
req = g_threaded_resolver_request_new (do_lookup_records,
free_lookup_records,
cancellable);
req->u.service.rrname = g_strdup (rrname);
resolve_async (gtr, req, callback, user_data, lookup_service_async);
req->u.records.rrname = g_strdup (rrname);
req->u.records.record_type = record_type;
resolve_async (gtr, req, callback, user_data, lookup_records_async);
}
static GList *
lookup_service_finish (GResolver *resolver,
lookup_records_finish (GResolver *resolver,
GAsyncResult *result,
GError **error)
GError **error)
{
GThreadedResolverRequest *req;
GList *targets;
GList *records;
req = resolve_finish (resolver, result, lookup_service_async, error);
targets = req->u.service.targets;
req->u.service.targets = NULL;
return targets;
req = resolve_finish (resolver, result, lookup_records_async, error);
records = req->u.records.results;
req->u.records.results = NULL;
return records;
}
@ -609,9 +633,9 @@ g_threaded_resolver_class_init (GThreadedResolverClass *threaded_class)
resolver_class->lookup_by_address = lookup_by_address;
resolver_class->lookup_by_address_async = lookup_by_address_async;
resolver_class->lookup_by_address_finish = lookup_by_address_finish;
resolver_class->lookup_service = lookup_service;
resolver_class->lookup_service_async = lookup_service_async;
resolver_class->lookup_service_finish = lookup_service_finish;
resolver_class->lookup_records = lookup_records;
resolver_class->lookup_records_async = lookup_records_async;
resolver_class->lookup_records_finish = lookup_records_finish;
object_class->finalize = finalize;
}

View File

@ -36,15 +36,20 @@ static GResolver *resolver;
static GCancellable *cancellable;
static GMainLoop *loop;
static int nlookups = 0;
static gboolean synchronous = FALSE;
static guint connectable_count = 0;
static GResolverRecordType record_type = 0;
static void G_GNUC_NORETURN
usage (void)
{
fprintf (stderr, "Usage: resolver [-s] [hostname | IP | service/protocol/domain ] ...\n");
fprintf (stderr, "Usage: resolver [-s] [-t MX|TXT|NS|SOA] rrname ...\n");
fprintf (stderr, " resolver [-s] -c NUMBER [hostname | IP | service/protocol/domain ]\n");
fprintf (stderr, " Use -s to do synchronous lookups.\n");
fprintf (stderr, " Use -c NUMBER (and only a single resolvable argument) to test GSocketConnectable.\n");
fprintf (stderr, " The given NUMBER determines how many times the connectable will be enumerated.\n");
fprintf (stderr, " Use -t with MX, TXT, NS or SOA to lookup DNS records of those types.\n");
exit (1);
}
@ -138,9 +143,9 @@ print_resolved_service (const char *service,
{
printf ("%s:%u (pri %u, weight %u)\n",
g_srv_target_get_hostname (t->data),
g_srv_target_get_port (t->data),
g_srv_target_get_priority (t->data),
g_srv_target_get_weight (t->data));
(guint)g_srv_target_get_port (t->data),
(guint)g_srv_target_get_priority (t->data),
(guint)g_srv_target_get_weight (t->data));
g_srv_target_free (t->data);
}
g_list_free (targets);
@ -151,12 +156,186 @@ print_resolved_service (const char *service,
G_UNLOCK (response);
}
static void
print_resolved_mx (const char *rrname,
GList *records,
GError *error)
{
const gchar *hostname;
guint16 priority;
GList *t;
G_LOCK (response);
printf ("Domain: %s\n", rrname);
if (error)
{
printf ("Error: %s\n", error->message);
g_error_free (error);
}
else if (!records)
{
printf ("no MX records\n");
}
else
{
for (t = records; t; t = t->next)
{
g_variant_get (t->data, "(q&s)", &priority, &hostname);
printf ("%s (pri %u)\n", hostname, (guint)priority);
g_variant_unref (t->data);
}
g_list_free (records);
}
printf ("\n");
done_lookup ();
G_UNLOCK (response);
}
static void
print_resolved_txt (const char *rrname,
GList *records,
GError *error)
{
const gchar **contents;
GList *t;
gint i;
G_LOCK (response);
printf ("Domain: %s\n", rrname);
if (error)
{
printf ("Error: %s\n", error->message);
g_error_free (error);
}
else if (!records)
{
printf ("no TXT records\n");
}
else
{
for (t = records; t; t = t->next)
{
if (t != records)
printf ("\n");
g_variant_get (t->data, "(^a&s)", &contents);
for (i = 0; contents[i] != NULL; i++)
printf ("%s\n", contents[i]);
g_variant_unref (t->data);
}
g_list_free (records);
}
printf ("\n");
done_lookup ();
G_UNLOCK (response);
}
static void
print_resolved_soa (const char *rrname,
GList *records,
GError *error)
{
GList *t;
const gchar *primary_ns;
const gchar *administrator;
guint32 serial, refresh, retry, expire, ttl;
G_LOCK (response);
printf ("Zone: %s\n", rrname);
if (error)
{
printf ("Error: %s\n", error->message);
g_error_free (error);
}
else if (!records)
{
printf ("no SOA records\n");
}
else
{
for (t = records; t; t = t->next)
{
g_variant_get (t->data, "(&s&suuuuu)", &primary_ns, &administrator,
&serial, &refresh, &retry, &expire, &ttl);
printf ("%s %s (serial %u, refresh %u, retry %u, expire %u, ttl %u)\n",
primary_ns, administrator, (guint)serial, (guint)refresh,
(guint)retry, (guint)expire, (guint)ttl);
g_variant_unref (t->data);
}
g_list_free (records);
}
printf ("\n");
done_lookup ();
G_UNLOCK (response);
}
static void
print_resolved_ns (const char *rrname,
GList *records,
GError *error)
{
GList *t;
const gchar *hostname;
G_LOCK (response);
printf ("Zone: %s\n", rrname);
if (error)
{
printf ("Error: %s\n", error->message);
g_error_free (error);
}
else if (!records)
{
printf ("no NS records\n");
}
else
{
for (t = records; t; t = t->next)
{
g_variant_get (t->data, "(&s)", &hostname);
printf ("%s\n", hostname);
g_variant_unref (t->data);
}
g_list_free (records);
}
printf ("\n");
done_lookup ();
G_UNLOCK (response);
}
static void
lookup_one_sync (const char *arg)
{
GError *error = NULL;
if (strchr (arg, '/'))
if (record_type != 0)
{
GList *records;
records = g_resolver_lookup_records (resolver, arg, record_type, cancellable, &error);
switch (record_type)
{
case G_RESOLVER_RECORD_MX:
print_resolved_mx (arg, records, error);
break;
case G_RESOLVER_RECORD_SOA:
print_resolved_soa (arg, records, error);
break;
case G_RESOLVER_RECORD_NS:
print_resolved_ns (arg, records, error);
break;
case G_RESOLVER_RECORD_TXT:
print_resolved_txt (arg, records, error);
break;
default:
g_warn_if_reached ();
break;
}
}
else if (strchr (arg, '/'))
{
GList *targets;
/* service/protocol/domain */
@ -244,6 +423,37 @@ lookup_service_callback (GObject *source, GAsyncResult *result,
print_resolved_service (service, targets, error);
}
static void
lookup_records_callback (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
const char *arg = user_data;
GError *error = NULL;
GList *records;
records = g_resolver_lookup_records_finish (resolver, result, &error);
switch (record_type)
{
case G_RESOLVER_RECORD_MX:
print_resolved_mx (arg, records, error);
break;
case G_RESOLVER_RECORD_SOA:
print_resolved_soa (arg, records, error);
break;
case G_RESOLVER_RECORD_NS:
print_resolved_ns (arg, records, error);
break;
case G_RESOLVER_RECORD_TXT:
print_resolved_txt (arg, records, error);
break;
default:
g_warn_if_reached ();
break;
}
}
static void
start_async_lookups (char **argv, int argc)
{
@ -251,7 +461,12 @@ start_async_lookups (char **argv, int argc)
for (i = 0; i < argc; i++)
{
if (strchr (argv[i], '/'))
if (record_type != 0)
{
g_resolver_lookup_records_async (resolver, argv[i], record_type,
cancellable, lookup_records_callback, argv[i]);
}
else if (strchr (argv[i], '/'))
{
/* service/protocol/domain */
char **parts = g_strsplit (argv[i], "/", 3);
@ -428,11 +643,42 @@ async_cancel (GIOChannel *source, GIOCondition cond, gpointer cancel)
}
#endif
static gboolean
record_type_arg (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
if (g_ascii_strcasecmp (value, "MX") == 0) {
record_type = G_RESOLVER_RECORD_MX;
} else if (g_ascii_strcasecmp (value, "TXT") == 0) {
record_type = G_RESOLVER_RECORD_TXT;
} else if (g_ascii_strcasecmp (value, "SOA") == 0) {
record_type = G_RESOLVER_RECORD_SOA;
} else if (g_ascii_strcasecmp (value, "NS") == 0) {
record_type = G_RESOLVER_RECORD_NS;
} else {
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Specify MX, TXT, NS or SOA for the special record lookup types");
return FALSE;
}
return TRUE;
}
static const GOptionEntry option_entries[] = {
{ "synchronous", 's', 0, G_OPTION_ARG_NONE, &synchronous, "Synchronous connections", NULL },
{ "connectable", 'c', 0, G_OPTION_ARG_INT, &connectable_count, "Connectable count", "C" },
{ "special-type", 't', 0, G_OPTION_ARG_CALLBACK, record_type_arg, "Record type like MX, TXT, NS or SOA", "RR" },
{ NULL },
};
int
main (int argc, char **argv)
{
gboolean synchronous = FALSE;
guint connectable_count = 0;
GOptionContext *context;
GError *error = NULL;
#ifdef G_OS_UNIX
GIOChannel *chan;
guint watch;
@ -440,22 +686,13 @@ main (int argc, char **argv)
g_type_init ();
/* FIXME: use GOptionContext */
while (argc >= 2 && argv[1][0] == '-')
context = g_option_context_new ("lookups ...");
g_option_context_add_main_entries (context, option_entries, NULL);
if (!g_option_context_parse (context, &argc, &argv, &error))
{
if (!strcmp (argv[1], "-s"))
synchronous = TRUE;
else if (!strcmp (argv[1], "-c"))
{
connectable_count = atoi (argv[2]);
argv++;
argc--;
}
else
usage ();
argv++;
argc--;
g_printerr ("%s\n", error->message);
g_error_free (error);
usage();
}
if (argc < 2 || (argc > 2 && connectable_count))