Blob Blame History Raw
From: Michael Santos <michael.santos@gmail.com>
Date: Sun, 18 Oct 2015 16:20:37 -0400
Subject: [PATCH] epmd: support IPv6 node registration

Allow IPv6 nodes to register with and query epmd. On systems with
IPv6 support:

* epmd listens on both the IPv4 and IPv6 ANY or loopback sockets

* the epmd cli client connects to epmd over the IPv6 loopback

* distributed nodes started with "-proto_dist inet6_tcp" will register
  with epmd over IPv6

To work on IPv6 capable systems that have IPv6 support disabled,
epmd ignores errors opening the socket if the protocol is not
supported. Similarly, the epmd client will fall back to IPv4 if the IPv6
socket is not available.

The interaction between IPv4 and IPv6 sockets depends on the platform:

* FreeBSD allows multiple "specific" sockets to bind the same port (such
  as 2 sockets listening to the same port on ANY and the loopback).
  Binding port 4369 to IPv4 and IPv6 sockets simulataneously is allowed.

* Linux does not allow the same port to be bound by different sockets.
  Setting the IPV6_V6ONLY socket option is required.

* Windows

  The behaviour differs depending on the version of Windows:

  http://msdn.microsoft.com/en-us/library/windows/desktop/bb513665(v=vs.85).aspx

  According to the site, sockets on Windows XP with Service Pack 1 (SP1)
  and Windows Server 2003 will only listen on either IPv4 or IPv6, so
  creating two sockets is required to service IPv4 and IPv6 traffic on
  the same port. The IPV6_V6ONLY socket option is not supported.

  For Windows Vista and later, a single socket can handle IPv4 and IPv6
  traffic for the same port. The IPV6_V6ONLY socket option is supported
  and is enabled for IPv6 sockets by default.

diff --git a/erts/doc/src/epmd.xml b/erts/doc/src/epmd.xml
index 28fcc8f..7f61804 100644
--- a/erts/doc/src/epmd.xml
+++ b/erts/doc/src/epmd.xml
@@ -37,7 +37,7 @@
   <comsummary>
   <p>Erlang Port Mapper Daemon</p>
   <taglist>
-  <tag><c><![CDATA[epmd [-d|-debug] [DbgExtra...] [-port No] [-daemon] [-relaxed_command_check]]]></c></tag>
+  <tag><c><![CDATA[epmd [-d|-debug] [DbgExtra...] [-address Addresses] [-port No] [-daemon] [-relaxed_command_check]]]></c></tag>
   <item>
   <p>Starts the port mapper daemon</p>
   </item>
diff --git a/erts/doc/src/erl.xml b/erts/doc/src/erl.xml
index ec4a0de..cdf8aef 100644
--- a/erts/doc/src/erl.xml
+++ b/erts/doc/src/erl.xml
@@ -382,6 +382,28 @@
           similar to <c><![CDATA[code:add_pathsz/1]]></c>. See
           <seealso marker="kernel:code">code(3)</seealso>.</p>
       </item>
+      <tag><c><![CDATA[-proto_dist Proto]]></c></tag>
+      <item>
+        <p>Specify a protocol for Erlang distribution.</p>
+	    <taglist>
+	      <tag><c>inet_tcp</c></tag>
+              <item>
+                  <p>TCP over IPv4 (the default)</p>
+              </item>
+	      <tag><c>inet_tls</c></tag>
+              <item>
+                  <p>distribution over TLS/SSL</p>
+              </item>
+	      <tag><c>inet6_tcp</c></tag>
+              <item>
+                  <p>TCP over IPv6</p>
+              </item>
+        </taglist>
+        <p>For example, to start up IPv6 distributed nodes:</p>
+<pre>
+% <input>erl -name test@ipv6node.example.com -proto_dist inet6_tcp</input>
+</pre>
+      </item>
       <tag><c><![CDATA[-remsh Node]]></c></tag>
       <item>
         <p>Starts Erlang with a remote shell connected to <c><![CDATA[Node]]></c>.</p>
diff --git a/erts/epmd/src/epmd.c b/erts/epmd/src/epmd.c
index 63ec18d..5513cb2 100644
--- a/erts/epmd/src/epmd.c
+++ b/erts/epmd/src/epmd.c
@@ -343,7 +343,7 @@ static void run_daemon(EpmdVars *g)
     for (fd = 0; fd < g->max_conn ; fd++) /* close all files ... */
         close(fd);
     /* Syslog on linux will try to write to whatever if we dont
-       inform it of that the log is closed. */
+       inform it that the log is closed. */
     closelog();
 
     /* These shouldn't be needed but for safety... */
diff --git a/erts/epmd/src/epmd_cli.c b/erts/epmd/src/epmd_cli.c
index a8fe865..6fc05e1 100644
--- a/erts/epmd/src/epmd_cli.c
+++ b/erts/epmd/src/epmd_cli.c
@@ -136,19 +136,33 @@ void epmd_call(EpmdVars *g,int what)
 static int conn_to_epmd(EpmdVars *g)
 {
     struct EPMD_SOCKADDR_IN address;
+    size_t salen = 0;
     int connect_sock;
-    
-    connect_sock = socket(FAMILY, SOCK_STREAM, 0);
-    if (connect_sock<0)
-	goto error;
+    unsigned short sport = g->port;
+
+#if defined(EPMD6)
+    SET_ADDR6(address, in6addr_loopback, sport);
+    salen = sizeof(struct sockaddr_in6);
+
+    connect_sock = socket(AF_INET6, SOCK_STREAM, 0);
+    if (connect_sock>=0) {
+
+    if (connect(connect_sock, (struct sockaddr*)&address, salen) == 0)
+	return connect_sock;
 
-    { /* store port number in unsigned short */
-      unsigned short sport = g->port;
-      SET_ADDR(address, EPMD_ADDR_LOOPBACK, sport);
+    close(connect_sock);
     }
+#endif
+    SET_ADDR(address, htonl(INADDR_LOOPBACK), sport);
+    salen = sizeof(struct sockaddr_in);
 
-    if (connect(connect_sock, (struct sockaddr*)&address, sizeof address) < 0) 
+    connect_sock = socket(AF_INET, SOCK_STREAM, 0);
+    if (connect_sock<0)
 	goto error;
+
+    if (connect(connect_sock, (struct sockaddr*)&address, salen) < 0)
+	goto error;
+
     return connect_sock;
 
  error:
diff --git a/erts/epmd/src/epmd_int.h b/erts/epmd/src/epmd_int.h
index 26100af..8f44c2a 100644
--- a/erts/epmd/src/epmd_int.h
+++ b/erts/epmd/src/epmd_int.h
@@ -55,6 +55,7 @@
 #  ifndef WINDOWS_H_INCLUDES_WINSOCK2_H
 #    include <winsock2.h>
 #  endif
+#  include <ws2tcpip.h>
 #  include <windows.h>
 #  include <process.h>
 #endif
@@ -130,6 +131,12 @@
 #  include <systemd/sd-daemon.h>
 #endif /* HAVE_SYSTEMD_DAEMON */
 
+#if defined(HAVE_IN6) && defined(AF_INET6)
+#if !defined(__WIN32__)
+#  define EPMD6
+#endif
+#endif
+
 /* ************************************************************************ */
 /* Replace some functions by others by making the function name a macro */
 
@@ -183,33 +190,53 @@
 /* ************************************************************************ */
 /* Macros that let us use IPv6                                              */
 
-#if defined(HAVE_IN6) && defined(AF_INET6) && defined(EPMD6)
+#if HAVE_IN6
+#  if ! defined(HAVE_IN6ADDR_ANY) || ! HAVE_IN6ADDR_ANY
+#    if HAVE_DECL_IN6ADDR_ANY_INIT
+static const struct in6_addr in6addr_any = { { IN6ADDR_ANY_INIT } };
+#    else
+static const struct in6_addr in6addr_any =
+    { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } };
+#    endif /* HAVE_IN6ADDR_ANY_INIT */
+#  endif /* ! HAVE_DECL_IN6ADDR_ANY */
+
+#  if ! defined(HAVE_IN6ADDR_LOOPBACK) || ! HAVE_IN6ADDR_LOOPBACK
+#    if HAVE_DECL_IN6ADDR_LOOPBACK_INIT
+static const struct in6_addr in6addr_loopback =
+    { { IN6ADDR_LOOPBACK_INIT } };
+#    else
+static const struct in6_addr in6addr_loopback =
+    { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } };
+#    endif /* HAVE_IN6ADDR_LOOPBACK_INIT */
+#  endif /* ! HAVE_DECL_IN6ADDR_LOOPBACK */
+#endif /* HAVE_IN6 */
+
+#define IS_ADDR_LOOPBACK(addr) ((addr).s_addr == htonl(INADDR_LOOPBACK))
 
-#define EPMD_SOCKADDR_IN sockaddr_in6
-#define EPMD_IN_ADDR in6_addr
-#define EPMD_S_ADDR s6_addr
-#define EPMD_ADDR_LOOPBACK in6addr_loopback.s6_addr
-#define EPMD_ADDR_ANY in6addr_any.s6_addr
+#if defined(EPMD6)
+
+#define EPMD_SOCKADDR_IN sockaddr_storage
 #define FAMILY AF_INET6
 
-#define SET_ADDR(dst, addr, port) do { \
-    memset((char*)&(dst), 0, sizeof(dst)); \
-    memcpy((char*)&(dst).sin6_addr.s6_addr, (char*)&(addr), 16); \
-    (dst).sin6_family = AF_INET6; \
-    (dst).sin6_flowinfo = 0; \
-    (dst).sin6_port = htons(port); \
+#define SET_ADDR6(dst, addr, port) do { \
+    struct sockaddr_in6 *sa = (struct sockaddr_in6 *)&(dst); \
+    memset(sa, 0, sizeof(dst)); \
+    sa->sin6_family = AF_INET6; \
+    sa->sin6_addr = (addr); \
+    sa->sin6_port = htons(port); \
  } while(0)
 
-#define IS_ADDR_LOOPBACK(addr) \
-    (memcmp((addr).s6_addr, in6addr_loopback.s6_addr, 16) == 0)
+#define SET_ADDR(dst, addr, port) do { \
+    struct sockaddr_in *sa = (struct sockaddr_in *)&(dst); \
+    memset(sa, 0, sizeof(dst)); \
+    sa->sin_family = AF_INET; \
+    sa->sin_addr.s_addr = (addr); \
+    sa->sin_port = htons(port); \
+ } while(0)
 
 #else /* Not IP v6 */
 
 #define EPMD_SOCKADDR_IN sockaddr_in
-#define EPMD_IN_ADDR in_addr
-#define EPMD_S_ADDR s_addr
-#define EPMD_ADDR_LOOPBACK htonl(INADDR_LOOPBACK)
-#define EPMD_ADDR_ANY htonl(INADDR_ANY)
 #define FAMILY AF_INET
 
 #define SET_ADDR(dst, addr, port) do { \
@@ -219,8 +246,6 @@
     (dst).sin_port = htons(port); \
  } while(0)
 
-#define IS_ADDR_LOOPBACK(addr) ((addr).s_addr == htonl(INADDR_LOOPBACK))
-
 #endif /* Not IP v6 */
 
 /* ************************************************************************ */
diff --git a/erts/epmd/src/epmd_srv.c b/erts/epmd/src/epmd_srv.c
index 8c8d730..28a2968 100644
--- a/erts/epmd/src/epmd_srv.c
+++ b/erts/epmd/src/epmd_srv.c
@@ -76,6 +76,7 @@ static time_t current_time(EpmdVars*);
 
 static Connection *conn_init(EpmdVars*);
 static int conn_open(EpmdVars*,int);
+static int conn_local_peer_check(EpmdVars*, int);
 static int conn_close_fd(EpmdVars*,int);
 
 static void node_init(EpmdVars*);
@@ -206,10 +207,11 @@ void run(EpmdVars *g)
 {
   struct EPMD_SOCKADDR_IN iserv_addr[MAX_LISTEN_SOCKETS];
   int listensock[MAX_LISTEN_SOCKETS];
-  int num_sockets;
+  int num_sockets = 0;
   int i;
   int opt;
   unsigned short sport = g->port;
+  int bound = 0;
 
   node_init(g);
   g->conn = conn_init(g);
@@ -252,64 +254,82 @@ void run(EpmdVars *g)
   if (g->addresses != NULL && /* String contains non-separator characters if: */
       g->addresses[strspn(g->addresses," ,")] != '\000')
     {
-      char *tmp;
-      char *token;
-      int loopback_ok = 0;
+      char *tmp = NULL;
+      char *token = NULL;
+
+      /* Always listen on the loopback. */
+      SET_ADDR(iserv_addr[num_sockets],htonl(INADDR_LOOPBACK),sport);
+      num_sockets++;
+#if defined(EPMD6)
+      SET_ADDR6(iserv_addr[num_sockets],in6addr_loopback,sport);
+      num_sockets++;
+#endif
 
-      if ((tmp = (char *)malloc(strlen(g->addresses) + 1)) == NULL)
+	  if ((tmp = strdup(g->addresses)) == NULL)
 	{
 	  dbg_perror(g,"cannot allocate memory");
 	  epmd_cleanup_exit(g,1);
 	}
-      strcpy(tmp,g->addresses);
 
-      for(token = strtok(tmp,", "), num_sockets = 0;
+      for(token = strtok(tmp,", ");
 	  token != NULL;
-	  token = strtok(NULL,", "), num_sockets++)
+	  token = strtok(NULL,", "))
 	{
-	  struct EPMD_IN_ADDR addr;
-#ifdef HAVE_INET_PTON
-	  int ret;
+	  struct in_addr addr;
+#if defined(EPMD6)
+	  struct in6_addr addr6;
+	  struct sockaddr_storage *sa = &iserv_addr[num_sockets];
 
-	  if ((ret = inet_pton(FAMILY,token,&addr)) == -1)
+	  if (inet_pton(AF_INET6,token,&addr6) == 1)
 	    {
-	      dbg_perror(g,"cannot convert IP address to network format");
-	      epmd_cleanup_exit(g,1);
+	      SET_ADDR6(iserv_addr[num_sockets],addr6,sport);
+	    }
+	  else if (inet_pton(AF_INET,token,&addr) == 1)
+	    {
+	      SET_ADDR(iserv_addr[num_sockets],addr.s_addr,sport);
+	    }
+	  else
+#else
+	  if ((addr.s_addr = inet_addr(token)) != INADDR_NONE)
+	    {
+	      SET_ADDR(iserv_addr[num_sockets],addr.s_addr,sport);
 	    }
-	  else if (ret == 0)
-#elif !defined(EPMD6)
-	  if ((addr.EPMD_S_ADDR = inet_addr(token)) == INADDR_NONE)
+	  else
 #endif
 	    {
 	      dbg_tty_printf(g,0,"cannot parse IP address \"%s\"",token);
 	      epmd_cleanup_exit(g,1);
 	    }
 
+#if defined(EPMD6)
+	  if (sa->ss_family == AF_INET6 && IN6_IS_ADDR_LOOPBACK(&addr6))
+	      continue;
+
+	  if (sa->ss_family == AF_INET)
+#endif
 	  if (IS_ADDR_LOOPBACK(addr))
-	    loopback_ok = 1;
+	    continue;
+
+	  num_sockets++;
 
-	  if (num_sockets - loopback_ok == MAX_LISTEN_SOCKETS - 1)
+	  if (num_sockets >= MAX_LISTEN_SOCKETS)
 	    {
 	      dbg_tty_printf(g,0,"cannot listen on more than %d IP addresses",
 			     MAX_LISTEN_SOCKETS);
 	      epmd_cleanup_exit(g,1);
 	    }
-
-	  SET_ADDR(iserv_addr[num_sockets],addr.EPMD_S_ADDR,sport);
 	}
 
       free(tmp);
-
-      if (!loopback_ok)
-	{
-	  SET_ADDR(iserv_addr[num_sockets],EPMD_ADDR_LOOPBACK,sport);
-	  num_sockets++;
-	}
     }
   else
     {
-      SET_ADDR(iserv_addr[0],EPMD_ADDR_ANY,sport);
-      num_sockets = 1;
+      SET_ADDR(iserv_addr[num_sockets],htonl(INADDR_ANY),sport);
+      num_sockets++;
+#if defined(EPMD6)
+      SET_ADDR6(iserv_addr[num_sockets],in6addr_any,sport);
+      num_sockets++;
+#endif
     }
 #ifdef HAVE_SYSTEMD_DAEMON
     }
@@ -340,13 +360,39 @@ void run(EpmdVars *g)
 #endif /* HAVE_SYSTEMD_DAEMON */
   for (i = 0; i < num_sockets; i++)
     {
-      if ((listensock[i] = socket(FAMILY,SOCK_STREAM,0)) < 0)
+      struct sockaddr *sa = (struct sockaddr *)&iserv_addr[i];
+#if defined(EPMD6)
+      size_t salen = (sa->sa_family == AF_INET6 ?
+              sizeof(struct sockaddr_in6) :
+              sizeof(struct sockaddr_in));
+#else
+      size_t salen = sizeof(struct sockaddr_in);
+#endif
+
+      if ((listensock[i] = socket(sa->sa_family,SOCK_STREAM,0)) < 0)
 	{
-	  dbg_perror(g,"error opening stream socket");
-	  epmd_cleanup_exit(g,1);
+	  switch (errno) {
+	      case EAFNOSUPPORT:
+	      case EPROTONOSUPPORT:
+	          continue;
+	      default:
+	          dbg_perror(g,"error opening stream socket");
+	          epmd_cleanup_exit(g,1);
+	  }
 	}
       g->listenfd[i] = listensock[i];
-  
+
+#if HAVE_DECL_IPV6_V6ONLY
+      opt = 1;
+      if (sa->sa_family == AF_INET6 &&
+          setsockopt(listensock[i],IPPROTO_IPV6,IPV6_V6ONLY,&opt,
+              sizeof(opt)) <0)
+	{
+	  dbg_perror(g,"can't set IPv6 only socket option");
+	  epmd_cleanup_exit(g,1);
+	}
+#endif
+
       /*
        * Note that we must not enable the SO_REUSEADDR on Windows,
        * because addresses will be reused even if they are still in use.
@@ -378,8 +424,7 @@ void run(EpmdVars *g)
 	dbg_perror(g,"failed to set non-blocking mode of listening socket %d",
 		   listensock[i]);
 
-      if (bind(listensock[i], (struct sockaddr*) &iserv_addr[i],
-	  sizeof(iserv_addr[i])) < 0)
+      if (bind(listensock[i], (struct sockaddr*) &iserv_addr[i], salen) < 0)
 	{
 	  if (errno == EADDRINUSE)
 	    {
@@ -394,12 +439,18 @@ void run(EpmdVars *g)
 	    }
 	}
 
+      bound++;
+
       if(listen(listensock[i], SOMAXCONN) < 0) {
           dbg_perror(g,"failed to listen on socket");
           epmd_cleanup_exit(g,1);
       }
       select_fd_set(g, listensock[i]);
     }
+  if (bound == 0) {
+      dbg_perror(g,"unable to bind any address");
+      epmd_cleanup_exit(g,1);
+  }
 #ifdef HAVE_SYSTEMD_DAEMON
     }
     sd_notifyf(0, "READY=1\n"
@@ -1007,15 +1058,6 @@ static int conn_open(EpmdVars *g,int fd)
 
   for (i = 0; i < g->max_conn; i++) {
     if (g->conn[i].open == EPMD_FALSE) {
-      struct sockaddr_in si;
-      struct sockaddr_in di;
-#ifdef HAVE_SOCKLEN_T
-      socklen_t st;
-#else
-      int st;
-#endif
-      st = sizeof(si);
-
       g->active_conn++;
       s = &g->conn[i];
      
@@ -1026,20 +1068,7 @@ static int conn_open(EpmdVars *g,int fd)
       s->open = EPMD_TRUE;
       s->keep = EPMD_FALSE;
 
-      /* Determine if connection is from localhost */
-      if (getpeername(s->fd,(struct sockaddr*) &si,&st) ||
-	  st < sizeof(si)) {
-	  /* Failure to get peername is regarded as non local host */
-	  s->local_peer = EPMD_FALSE;
-      } else {
-	  /* Only 127.x.x.x and connections from the host's IP address
-	     allowed, no false positives */
-	  s->local_peer =
-	      (((((unsigned) ntohl(si.sin_addr.s_addr)) & 0xFF000000U) ==
-	       0x7F000000U) ||
-	       (getsockname(s->fd,(struct sockaddr*) &di,&st) ?
-	       EPMD_FALSE : si.sin_addr.s_addr == di.sin_addr.s_addr));
-      }
+      s->local_peer = conn_local_peer_check(g, s->fd);
       dbg_tty_printf(g,2,(s->local_peer) ? "Local peer connected" :
 		     "Non-local peer connected");
 
@@ -1047,7 +1076,7 @@ static int conn_open(EpmdVars *g,int fd)
       s->got  = 0;
       s->mod_time = current_time(g); /* Note activity */
 
-      s->buf = (char *)malloc(INBUF_SIZE);
+      s->buf = malloc(INBUF_SIZE);
 
       if (s->buf == NULL) {
 	dbg_printf(g,0,"epmd: Insufficient memory");
@@ -1065,6 +1094,60 @@ static int conn_open(EpmdVars *g,int fd)
   return EPMD_FALSE;
 }
 
+static int conn_local_peer_check(EpmdVars *g, int fd)
+{
+  struct EPMD_SOCKADDR_IN si;
+  struct EPMD_SOCKADDR_IN di;
+
+  struct sockaddr_in *si4 = (struct sockaddr_in *)&si;
+  struct sockaddr_in *di4 = (struct sockaddr_in *)&di;
+
+#if defined(EPMD6)
+  struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&si;
+  struct sockaddr_in6 *di6 = (struct sockaddr_in6 *)&di;
+#endif
+
+#ifdef HAVE_SOCKLEN_T
+  socklen_t st;
+#else
+  int st;
+#endif
+
+  st = sizeof(si);
+
+  /* Determine if connection is from localhost */
+  if (getpeername(fd,(struct sockaddr*) &si,&st) ||
+	  st > sizeof(si)) {
+	  /* Failure to get peername is regarded as non local host */
+	  return EPMD_FALSE;
+  }
+
+  /* Only 127.x.x.x and connections from the host's IP address
+	 allowed, no false positives */
+#if defined(EPMD6)
+  if (si.ss_family == AF_INET6 && IN6_IS_ADDR_LOOPBACK(&(si6->sin6_addr)))
+	  return EPMD_TRUE;
+
+  if (si.ss_family == AF_INET)
+#endif
+  if ((((unsigned) ntohl(si4->sin_addr.s_addr)) & 0xFF000000U) ==
+	  0x7F000000U)
+	  return EPMD_TRUE;
+
+  if (getsockname(fd,(struct sockaddr*) &di,&st))
+	  return EPMD_FALSE;
+
+#if defined(EPMD6)
+  if (si.ss_family == AF_INET6)
+      return IN6_ARE_ADDR_EQUAL( &(si6->sin6_addr), &(di6->sin6_addr));
+  if (si.ss_family == AF_INET)
+#endif
+  return si4->sin_addr.s_addr == di4->sin_addr.s_addr;
+#if defined(EPMD6)
+  return EPMD_FALSE;
+#endif
+}
+
 static int conn_close_fd(EpmdVars *g,int fd)
 {
   int i;
diff --git a/erts/epmd/test/epmd_SUITE.erl b/erts/epmd/test/epmd_SUITE.erl
index e8bbfdb..f3c0ada 100644
--- a/erts/epmd/test/epmd_SUITE.erl
+++ b/erts/epmd/test/epmd_SUITE.erl
@@ -43,6 +43,7 @@
 -export(
    [
     register_name/1,
+    register_name_ipv6/1,
     register_names_1/1,
     register_names_2/1,
     register_duplicate_name/1,
@@ -111,7 +112,8 @@
 suite() -> [{ct_hooks,[ts_install_cth]}].
 
 all() -> 
-    [register_name, register_names_1, register_names_2,
+    [register_name, register_name_ipv6,
+     register_names_1, register_names_2,
      register_duplicate_name, unicode_name, long_unicode_name,
      get_port_nr, slow_get_port_nr,
      unregister_others_name_1, unregister_others_name_2,
@@ -169,6 +171,24 @@ register_name(Config) when is_list(Config) ->
     ?line ok = close(Sock),			% Unregister
     ok.
 
+register_name_ipv6(doc) ->
+    ["Register a name over IPv6"];
+register_name_ipv6(suite) ->
+    [];
+register_name_ipv6(Config) when is_list(Config) ->
+    % Test if the host has an IPv6 loopback address
+    Res = gen_tcp:listen(0, [inet6, {ip, {0,0,0,0,0,0,0,1}}]),
+    case Res of
+    {ok,LSock} ->
+	    gen_tcp:close(LSock),
+	    ?line ok = epmdrun(),
+	    ?line {ok,Sock} = register_node6("foobar6"),
+	    ?line ok = close(Sock),         % Unregister
+	    ok;
+    _Error ->
+	    {skip, "Host does not have an IPv6 loopback address"}
+    end.
+
 register_names_1(doc) ->
     ["Register and unregister two nodes"];
 register_names_1(suite) ->
@@ -242,13 +262,18 @@ register_node(Name) ->
 register_node(Name,Port) ->
     register_node_v2(Port,$M,0,5,5,Name,"").
 
+register_node6(Name) ->
+    register_node_v2({0,0,0,0,0,0,0,1},?DUMMY_PORT,$M,0,5,5,Name,"").
+
 register_node_v2(Port, NodeType, Prot, HVsn, LVsn, Name, Extra) ->
+    register_node_v2("localhost", Port, NodeType, Prot, HVsn, LVsn, Name, Extra).
+register_node_v2(Addr, Port, NodeType, Prot, HVsn, LVsn, Name, Extra) ->
     Utf8Name = unicode:characters_to_binary(Name),
     Req = [?EPMD_ALIVE2_REQ, put16(Port), NodeType, Prot,
 	   put16(HVsn), put16(LVsn),
 	   put16(size(Utf8Name)), binary_to_list(Utf8Name),
 	   size16(Extra), Extra],
-    case send_req(Req) of
+    case send_req(Req, Addr) of
 	{ok,Sock} ->
 	    case recv(Sock,4) of
 		{ok, [?EPMD_ALIVE2_RESP,_Res=0,_C0,_C1]} ->
@@ -1151,7 +1176,9 @@ send_direct(Sock, Bytes) ->
     end.
 
 send_req(Req) ->
-    case connect() of
+    send_req(Req, "localhost").
+send_req(Req, Addr) ->
+    case connect(Addr) of
 	{ok,Sock} ->
 	    case send(Sock, [size16(Req), Req]) of
 		ok ->
diff --git a/lib/kernel/src/erl_epmd.erl b/lib/kernel/src/erl_epmd.erl
index 55ce9a7..c6202dd 100644
--- a/lib/kernel/src/erl_epmd.erl
+++ b/lib/kernel/src/erl_epmd.erl
@@ -32,7 +32,7 @@
 %% External exports
 -export([start/0, start_link/0, stop/0, port_please/2, 
 	 port_please/3, names/0, names/1,
-	 register_node/2, open/0, open/1, open/2]).
+	 register_node/2, register_node/3, open/0, open/1, open/2]).
 
 %% gen_server callbacks
 -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 
@@ -102,7 +102,9 @@ names(EpmdAddr) ->
 
 
 register_node(Name, PortNo) ->
-    gen_server:call(erl_epmd, {register, Name, PortNo}, infinity).
+    register_node(Name, PortNo, inet).
+register_node(Name, PortNo, Family) ->
+    gen_server:call(erl_epmd, {register, Name, PortNo, Family}, infinity).
 
 %%%----------------------------------------------------------------------
 %%% Callback functions from gen_server
@@ -120,10 +122,10 @@ init(_) ->
 -spec handle_call(calls(), term(), state()) ->
         {'reply', term(), state()} | {'stop', 'shutdown', 'ok', state()}.
 
-handle_call({register, Name, PortNo}, _From, State) ->
+handle_call({register, Name, PortNo, Family}, _From, State) ->
     case State#state.socket of
 	P when P < 0 ->
-	    case do_register_node(Name, PortNo) of
+	    case do_register_node(Name, PortNo, Family) of
 		{alive, Socket, Creation} ->
 		    S = State#state{socket = Socket,
 				    port_no = PortNo,
@@ -206,8 +208,12 @@ open({A,B,C,D,E,F,G,H}=EpmdAddr, Timeout) when ?ip6(A,B,C,D,E,F,G,H) ->
 close(Socket) ->
     gen_tcp:close(Socket).
 
-do_register_node(NodeName, TcpPort) ->
-    case open() of
+do_register_node(NodeName, TcpPort, Family) ->
+    Localhost = case Family of
+        inet -> open({127,0,0,1});
+        inet6 -> open({0,0,0,0,0,0,0,1})
+    end,
+    case Localhost of
 	{ok, Socket} ->
 	    Name = to_string(NodeName),
 	    Extra = "",