Resolve .local domains with getaddrinfo()
authorRyan Dahl <ry@tinyclouds.org>
Wed, 30 Jun 2010 04:20:32 +0000 (21:20 -0700)
committerRyan Dahl <ry@tinyclouds.org>
Wed, 30 Jun 2010 05:15:28 +0000 (22:15 -0700)
C-Ares doesn't go through the Name Service Switch (NSS) and thus can't
resolve certain classes of names. Generally this doesn't matter and the
whole idea of NSS is rather annoying. Nevertheless until C-Ares gets better
support, adding this hack to go through getaddrinfo() for .local domain look
up.

This reverts commit 9926dacd14c39276299712ced4a83fb043f27162.

lib/dns.js
src/node_net.cc

index e57b9b4..52aa034 100644 (file)
@@ -97,6 +97,8 @@ exports.getHostByName = function (domain, callback) {
   channel.getHostByName(domain, dns.AF_INET, callback);
 };
 
+var net;
+
 // Easy DNS A/AAAA look up
 exports.lookup = function (domain, callback) {
   var addressType = dns.isIP(domain);
@@ -105,19 +107,27 @@ exports.lookup = function (domain, callback) {
       callback(null, domain, addressType);
     });
   } else {
-    channel.getHostByName(domain, dns.AF_INET, function (err, domains4) {
-      if (domains4 && domains4.length) {
-        callback(null, domains4[0], 4);
-      } else {
-        channel.getHostByName(domain, dns.AF_INET6, function (err, domains6) {
-          if (domains6 && domains6.length) {
-            callback(null, domains6[0], 6);
-          } else {
-            callback(err, []);
-          }
-        });
-      }
-    });
+    if (/\w\.local\.?$/.test(domain) ) {
+      // ANNOYING: In the case of mDNS domains use NSS in the thread pool.
+      // I wish c-ares had better support.
+      process.binding('net').getaddrinfo(domain, 4, function (err, domains4) {
+        callback(err, domains4[0], 4);
+      });
+    } else {
+      channel.getHostByName(domain, dns.AF_INET, function (err, domains4) {
+        if (domains4 && domains4.length) {
+          callback(null, domains4[0], 4);
+        } else {
+          channel.getHostByName(domain, dns.AF_INET6, function (err, domains6) {
+            if (domains6 && domains6.length) {
+              callback(null, domains6[0], 6);
+            } else {
+              callback(err, []);
+            }
+          });
+        }
+      });
+    }
   }
 };
 
index bbc1540..cfeeb51 100644 (file)
@@ -14,6 +14,8 @@
 #include <fcntl.h>
 #include <arpa/inet.h> /* inet_pton */
 
+#include <netdb.h>
+
 #include <netinet/in.h>
 #include <netinet/tcp.h>
 
@@ -1015,6 +1017,163 @@ static Handle<Value> SetKeepAlive(const Arguments& args) {
   return Undefined();
 }
 
+//
+// G E T A D D R I N F O
+//
+
+
+struct resolve_request {
+  Persistent<Function> cb;
+  struct addrinfo *address_list;
+  int ai_family; // AF_INET or AF_INET6
+  char hostname[1];
+};
+
+#ifndef EAI_NODATA // EAI_NODATA is deprecated, FreeBSD already thrown it away in favor of EAI_NONAME
+#define EAI_NODATA EAI_NONAME
+#endif
+
+static int AfterResolve(eio_req *req) {
+  ev_unref(EV_DEFAULT_UC);
+
+  struct resolve_request * rreq = (struct resolve_request *)(req->data);
+
+  HandleScope scope;
+  Local<Value> argv[2];
+
+  if (req->result != 0) {
+    argv[1] = Array::New();
+    if (req->result == EAI_NODATA) {
+      argv[0] = Local<Value>::New(Null());
+    } else {
+      argv[0] = ErrnoException(req->result,
+                               "getaddrinfo",
+                               gai_strerror(req->result));
+    }
+  } else {
+    struct addrinfo *address;
+    int n = 0;
+
+    for (address = rreq->address_list; address; address = address->ai_next) { n++; }
+
+    Local<Array> results = Array::New(n);
+
+    char ip[INET6_ADDRSTRLEN];
+    const char *addr;
+
+    n = 0;
+    address = rreq->address_list;
+    while (address) {
+      assert(address->ai_socktype == SOCK_STREAM);
+      assert(address->ai_family == AF_INET || address->ai_family == AF_INET6);
+      addr = ( address->ai_family == AF_INET
+             ? (char *) &((struct sockaddr_in *) address->ai_addr)->sin_addr
+             : (char *) &((struct sockaddr_in6 *) address->ai_addr)->sin6_addr
+             );
+      const char *c = inet_ntop(address->ai_family, addr, ip, INET6_ADDRSTRLEN);
+      Local<String> s = String::New(c);
+      results->Set(Integer::New(n), s);
+
+      n++;
+      address = address->ai_next;
+    }
+
+    argv[0] = Local<Value>::New(Null());
+    argv[1] = results;
+  }
+
+  TryCatch try_catch;
+
+  rreq->cb->Call(Context::GetCurrent()->Global(), 2, argv);
+
+  if (try_catch.HasCaught()) {
+    FatalException(try_catch);
+  }
+
+  if (rreq->address_list) freeaddrinfo(rreq->address_list);
+  rreq->cb.Dispose(); // Dispose of the persistent handle
+  free(rreq);
+
+  return 0;
+}
+
+
+static int Resolve(eio_req *req) {
+  // Note: this function is executed in the thread pool! CAREFUL
+  struct resolve_request * rreq = (struct resolve_request *) req->data;
+
+  struct addrinfo hints;
+  memset(&hints, 0, sizeof(struct addrinfo));
+  hints.ai_family = rreq->ai_family;
+  hints.ai_socktype = SOCK_STREAM;
+
+  req->result = getaddrinfo((char*)rreq->hostname,
+                            NULL,
+                            &hints,
+                            &(rreq->address_list));
+  return 0;
+}
+
+
+static Handle<Value> GetAddrInfo(const Arguments& args) {
+  HandleScope scope;
+
+  String::Utf8Value hostname(args[0]->ToString());
+
+  int type = args[1]->Int32Value();
+  int fam = AF_INET;
+  switch (type) {
+    case 4:
+      fam = AF_INET;
+      break;
+    case 6:
+      fam = AF_INET6;
+      break;
+    default:
+      return ThrowException(Exception::TypeError(
+            String::New("Second argument must be an integer 4 or 6")));
+  }
+
+  if (!args[2]->IsFunction()) {
+    return ThrowException(Exception::TypeError(
+          String::New("Thrid argument must be a callback")));
+  }
+
+  Local<Function> cb = Local<Function>::Cast(args[2]);
+
+  struct resolve_request *rreq = (struct resolve_request *)
+    calloc(1, sizeof(struct resolve_request) + hostname.length());
+
+  if (!rreq) {
+    V8::LowMemoryNotification();
+    return ThrowException(Exception::Error(
+          String::New("Could not allocate enough memory")));
+  }
+
+  strcpy(rreq->hostname, *hostname);
+  rreq->cb = Persistent<Function>::New(cb);
+  rreq->ai_family = fam;
+
+  // For the moment I will do DNS lookups in the eio thread pool. This is
+  // sub-optimal and cannot handle massive numbers of requests.
+  //
+  // (One particularly annoying problem is that the pthread stack size needs
+  // to be increased dramatically to handle getaddrinfo() see X_STACKSIZE in
+  // wscript ).
+  //
+  // In the future I will move to a system using c-ares:
+  // http://lists.schmorp.de/pipermail/libev/2009q1/000632.html
+  eio_custom(Resolve, EIO_PRI_DEFAULT, AfterResolve, rreq);
+
+  // There will not be any active watchers from this object on the event
+  // loop while getaddrinfo() runs. If the only thing happening in the
+  // script was this hostname resolution, then the event loop would drop
+  // out. Thus we need to add ev_ref() until AfterResolve().
+  ev_ref(EV_DEFAULT_UC);
+
+  return Undefined();
+}
+
 
 static Handle<Value> IsIP(const Arguments& args) {
   HandleScope scope;
@@ -1084,6 +1243,7 @@ void InitNet(Handle<Object> target) {
   NODE_SET_METHOD(target, "setKeepAlive", SetKeepAlive);
   NODE_SET_METHOD(target, "getsockname", GetSockName);
   NODE_SET_METHOD(target, "getpeername", GetPeerName);
+  NODE_SET_METHOD(target, "getaddrinfo", GetAddrInfo);
   NODE_SET_METHOD(target, "isIP", IsIP);
   NODE_SET_METHOD(target, "errnoException", CreateErrnoException);