diff --git a/erlang.spec b/erlang.spec index 4c859f2..bb87f85 100644 --- a/erlang.spec +++ b/erlang.spec @@ -16,7 +16,7 @@ Name: erlang Version: 18.2.2 -Release: 1%{?dist} +Release: 2%{?dist} Summary: General-purpose programming language and runtime environment Group: Development/Languages @@ -72,6 +72,9 @@ Patch7: otp-0007-Split-off-webtool-dependency-from-tools.patch # Fedora specific patch # Add patch to crash dump on large distribution Patch8: otp-0008-Add-patch-to-crash-dump-on-large-distribution.patch +# Fedora specific patch +# epmd: support IPv6 node registration +Patch9: otp-0009-epmd-support-IPv6-node-registration.patch # end of autogenerated patch tag list BuildRequires: flex @@ -897,6 +900,7 @@ Erlang mode for XEmacs (source lisp files). %patch6 -p1 -b .Do_not_install_erlang_sources %patch7 -p1 -b .Split_off_webtool_dependency_from_tools %patch8 -p1 -b .Add_patch_to_crash_dump_on_large_distribution +%patch9 -p1 -b .epmd_support_IPv6_node_registration # end of autogenerated prep patch list # FIXME we should come up with a better solution @@ -2218,6 +2222,9 @@ useradd -r -g epmd -d /tmp -s /sbin/nologin \ %changelog +* Sun Jan 17 2016 John Eckersberg - 18.2.2-2 +- Add patch for epmd ipv6 support (rhbz#1299253) + * Mon Jan 11 2016 Peter Lemenkov - 18.2.2-1 - Ver. 18.2.2 diff --git a/otp-0009-epmd-support-IPv6-node-registration.patch b/otp-0009-epmd-support-IPv6-node-registration.patch new file mode 100644 index 0000000..0347c7e --- /dev/null +++ b/otp-0009-epmd-support-IPv6-node-registration.patch @@ -0,0 +1,701 @@ +From: Michael Santos +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 @@ + +

Erlang Port Mapper Daemon

+ +- ++ + +

Starts the port mapper daemon

+
+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 . See + code(3).

+ ++ ++ ++

Specify a protocol for Erlang distribution.

++ ++ inet_tcp ++ ++

TCP over IPv4 (the default)

++
++ inet_tls ++ ++

distribution over TLS/SSL

++
++ inet6_tcp ++ ++

TCP over IPv6

++
++
++

For example, to start up IPv6 distributed nodes:

++
++% erl -name test@ipv6node.example.com -proto_dist inet6_tcp
++
++
+ + +

Starts Erlang with a remote shell connected to .

+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 + # endif ++# include + # include + # include + #endif +@@ -130,6 +131,12 @@ + # include + #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 = "",