qulogic/pidgin

Implement mdns_browse using DNS Service API.
draft win-mdns
2021-01-05, Elliott Sales de Andrade
f4d6dbbdc4af
Parents 9b049f9577a1
Children 29414c681827
Implement mdns_browse using DNS Service API.
--- a/libpurple/protocols/bonjour/mdns_dnsapi.c Tue Jan 05 05:51:54 2021 -0500
+++ b/libpurple/protocols/bonjour/mdns_dnsapi.c Tue Jan 05 06:13:59 2021 -0500
@@ -34,12 +34,28 @@
typedef struct {
DNS_SERVICE_INSTANCE instance;
DNS_SERVICE_CANCEL register_cancel;
+
+ DNS_SERVICE_CANCEL browse_cancel;
} Win32SessionImplData;
typedef struct {
+ PDNS_RECORD dns_record;
+ gpointer context;
+} DnsAPIRecordData;
+
+typedef struct {
gpointer unused;
} Win32BuddyImplData;
+static void
+_mdns_record_data_free(DnsAPIRecordData *data)
+{
+ if (data->dns_record) {
+ DnsRecordListFree(data->dns_record, DnsFreeRecordList);
+ }
+ g_free(data);
+}
+
static gboolean
_mdns_service_register_main_context(gpointer data)
{
@@ -66,6 +82,204 @@
GUINT_TO_POINTER(status));
}
+static gboolean
+_mdns_service_browse_error(gpointer data)
+{
+ DWORD status = GPOINTER_TO_UINT(data);
+
+ purple_debug_error("bonjour", "service browser - error (%ld)", status);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gchar *
+_mdns_str_to_utf8(DNS_CHARSET charset, gpointer data)
+{
+ if (charset == DnsCharSetUtf8) {
+ return g_strdup((const gchar *)data);
+ } else if (charset == DnsCharSetUnicode) {
+ return g_utf16_to_utf8((const gunichar2 *)data, -1, NULL, NULL, NULL);
+ } else if (charset == DnsCharSetAnsi) {
+ return g_locale_to_utf8((const gchar *)data, -1, NULL, NULL, NULL);
+ } else {
+ return g_strdup("unknown");
+ }
+}
+
+static gchar *
+_mdns_namehost_to_service_name(DNS_CHARSET charset, gpointer data)
+{
+ gchar *name = _mdns_str_to_utf8(charset, data);
+ size_t len = strlen(name);
+ if (len > strlen("." LINK_LOCAL_DOMAIN_NAME)) {
+ gchar *end = name + (len - strlen("." LINK_LOCAL_DOMAIN_NAME));
+ if (g_str_equal(end, "." LINK_LOCAL_DOMAIN_NAME)) {
+ *end = '\0';
+ }
+ }
+ return name;
+}
+
+static gboolean
+_mdns_service_browse_parse(gpointer _data)
+{
+ DnsAPIRecordData *data = _data;
+ PDNS_RECORD pDnsRecord = data->dns_record;
+ PurpleAccount *account = PURPLE_ACCOUNT(data->context);
+
+ if (pDnsRecord->Flags.DW & DNSREC_DELETE) {
+ } else {
+ gchar *service_name = NULL;
+ gchar *hostname = NULL;
+ guint16 port = 0;
+ gchar *ip = NULL;
+ DNS_CHARSET txt_charset = DnsCharSetUnknown;
+#ifdef UNICODE
+ PDNS_TXT_DATAW txt = NULL;
+#else
+ PDNS_TXT_DATAA txt = NULL;
+#endif
+ BonjourBuddy *bb = NULL;
+
+ for (; pDnsRecord; pDnsRecord = pDnsRecord->pNext) {
+ gchar *record_name = _mdns_str_to_utf8(pDnsRecord->Flags.S.CharSet,
+ pDnsRecord->pName);
+
+ switch (pDnsRecord->wType) {
+ case DNS_TYPE_PTR:
+ /* The PTR record tells us a Buddy name exists. */
+ if (g_str_equal(record_name, LINK_LOCAL_DOMAIN_NAME)) {
+ service_name = _mdns_namehost_to_service_name(
+ pDnsRecord->Flags.S.CharSet,
+ pDnsRecord->Data.PTR.pNameHost);
+
+ /* A presence service instance has been discovered...
+ * check it isn't us! */
+ if (!purple_utf8_strcasecmp(service_name,
+ bonjour_get_jid(account))) {
+ g_free(service_name);
+ g_free(record_name);
+ return G_SOURCE_REMOVE;
+ }
+ }
+ break;
+
+ case DNS_TYPE_SRV:
+ /* The SRV record tells us the hostname and port of the
+ * Buddy. */
+ hostname =
+ _mdns_str_to_utf8(pDnsRecord->Flags.S.CharSet,
+ pDnsRecord->Data.SRV.pNameTarget);
+ port = pDnsRecord->Data.SRV.wPort;
+ break;
+
+ case DNS_TYPE_A:
+ /* The A record tells us the IPv4 address of the hostname.
+ */
+ if (hostname != NULL &&
+ g_str_equal(hostname, record_name)) {
+ GInetAddress *addr = g_inet_address_new_from_bytes(
+ (const guint8 *)&pDnsRecord->Data.A.IpAddress,
+ G_SOCKET_FAMILY_IPV4);
+ if (G_IS_INET_ADDRESS(addr)) {
+ ip = g_inet_address_to_string(addr);
+ }
+ g_clear_object(&addr);
+ }
+ break;
+
+ case DNS_TYPE_AAAA:
+ /* The AAAA record tells us the IPv6 address of the
+ * hostname. */
+ if (hostname != NULL &&
+ g_str_equal(hostname, record_name)) {
+ GInetAddress *addr = g_inet_address_new_from_bytes(
+ (const guint8 *)&pDnsRecord->Data.AAAA
+ .Ip6Address,
+ G_SOCKET_FAMILY_IPV6);
+ if (G_IS_INET_ADDRESS(addr)) {
+ ip = g_inet_address_to_string(addr);
+ }
+ g_clear_object(&addr);
+ }
+ break;
+
+ case DNS_TYPE_TEXT:
+ /* The TEXT record gives us additional record information.
+ */
+ txt = &pDnsRecord->Data.TXT;
+ txt_charset = pDnsRecord->Flags.S.CharSet;
+ break;
+
+ default:
+ purple_debug_warning("bonjour",
+ "Received unknown DNS record type %d",
+ pDnsRecord->wType);
+ break;
+ }
+
+ g_free(record_name);
+ }
+
+ if (service_name == NULL || port == 0 || ip == NULL) {
+ purple_debug_error("bonjour",
+ "Received incomplete DNS record for '%s'",
+ service_name);
+ g_free(service_name);
+ g_free(hostname);
+ g_free(ip);
+ return G_SOURCE_REMOVE;
+ }
+ purple_debug_info("bonjour",
+ "Received new record for '%s' at '%s' via %s:%d",
+ service_name, hostname, ip, port);
+
+ bb = bonjour_buddy_new(service_name, account);
+ bb->port_p2pj = port;
+ bb->ips = g_slist_prepend(bb->ips, ip);
+ if (txt != NULL) {
+ DWORD i;
+ for (i = 0; i < txt->dwStringCount; i++) {
+ gchar *tmp =
+ _mdns_str_to_utf8(txt_charset, txt->pStringArray[i]);
+ gchar **parts = g_strsplit(tmp, "=", 2);
+ set_bonjour_buddy_value(bb, parts[0], parts[1],
+ strlen(parts[1]));
+ g_strfreev(parts);
+ g_free(tmp);
+ }
+ }
+ bonjour_buddy_add_to_purple(bb);
+ g_free(service_name);
+ g_free(hostname);
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+/* This callback occurs in a different thread; so all it does is pass the
+ * result to the main context thread, or Purple will break. */
+static void
+_mdns_service_browse_callback(DWORD status, PVOID pQueryContext,
+ PDNS_RECORD pDnsRecord)
+{
+ if (status == ERROR_SUCCESS) {
+ DnsAPIRecordData *data = g_new(DnsAPIRecordData, 1);
+ data->dns_record = pDnsRecord;
+ data->context = pQueryContext;
+ g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, 0,
+ G_SOURCE_FUNC(_mdns_service_browse_parse),
+ data,
+ (GDestroyNotify)_mdns_record_data_free);
+ } else {
+ g_timeout_add_seconds(0, G_SOURCE_FUNC(_mdns_service_browse_error),
+ GUINT_TO_POINTER(status));
+ if (pDnsRecord) {
+ DnsRecordListFree(pDnsRecord, DnsFreeRecordList);
+ }
+ }
+}
+
/****************************
* mdns_interface functions *
****************************/
@@ -223,10 +437,26 @@
dnsapi_mdns_browse(BonjourDnsSd *data)
{
Win32SessionImplData *idata = data->mdns_impl_data;
+ DNS_SERVICE_BROWSE_REQUEST browse;
+ DNS_STATUS status;
g_return_val_if_fail(idata != NULL, FALSE);
- return FALSE;
+ browse.Version = DNS_QUERY_REQUEST_VERSION1;
+ browse.InterfaceIndex = 0;
+ browse.QueryName = LINK_LOCAL_DOMAIN_NAME_W;
+ browse.pBrowseCallback = _mdns_service_browse_callback;
+ browse.pQueryContext = data->account;
+
+ status = DnsServiceBrowse(&browse, &idata->browse_cancel);
+ if (status != DNS_REQUEST_PENDING) {
+ purple_debug_error(
+ "bonjour",
+ "Error registering Local Link presence browser. (%ld)", status);
+ return FALSE;
+ }
+
+ return TRUE;
}
static void