diff options
author | Michael Santos <[email protected]> | 2015-10-18 16:20:37 -0400 |
---|---|---|
committer | Michael Santos <[email protected]> | 2016-02-10 16:57:13 -0500 |
commit | 40695d080b0dc0665b01803768ffc74ed2eca207 (patch) | |
tree | 3255088e5dd5e6cac2dd710a7bb996a98a2e1483 /erts | |
parent | 439292ad4afe73243852fe79d5d467c325f382bf (diff) | |
download | otp-40695d080b0dc0665b01803768ffc74ed2eca207.tar.gz otp-40695d080b0dc0665b01803768ffc74ed2eca207.tar.bz2 otp-40695d080b0dc0665b01803768ffc74ed2eca207.zip |
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.
Update the minimum supported version of Windows to Windows Vista to
support IPv6.
Diffstat (limited to 'erts')
-rw-r--r-- | erts/configure.in | 2 | ||||
-rw-r--r-- | erts/doc/src/epmd.xml | 2 | ||||
-rw-r--r-- | erts/doc/src/erl.xml | 22 | ||||
-rw-r--r-- | erts/epmd/src/epmd.c | 2 | ||||
-rw-r--r-- | erts/epmd/src/epmd_cli.c | 30 | ||||
-rw-r--r-- | erts/epmd/src/epmd_int.h | 63 | ||||
-rw-r--r-- | erts/epmd/src/epmd_srv.c | 201 | ||||
-rw-r--r-- | erts/epmd/test/epmd_SUITE.erl | 33 |
8 files changed, 262 insertions, 93 deletions
diff --git a/erts/configure.in b/erts/configure.in index 4fb725ff00..368c5638ae 100644 --- a/erts/configure.in +++ b/erts/configure.in @@ -468,7 +468,7 @@ case $host_os in win32) # The ethread library requires _WIN32_WINNT of at least 0x0403. # -D_WIN32_WINNT=* from CPPFLAGS is saved in ETHR_DEFS. - CPPFLAGS="$CPPFLAGS -D_WIN32_WINNT=0x0501 -DWINVER=0x0501" + CPPFLAGS="$CPPFLAGS -D_WIN32_WINNT=0x0600 -DWINVER=0x0600" ;; darwin*) CPPFLAGS="$CPPFLAGS -D_XOPEN_SOURCE" diff --git a/erts/doc/src/epmd.xml b/erts/doc/src/epmd.xml index 28fcc8f7af..7f61804bea 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 e8621fecc3..ed3e7e34c4 100644 --- a/erts/doc/src/erl.xml +++ b/erts/doc/src/erl.xml @@ -387,6 +387,28 @@ <p>Replaces the path specified in the boot script. See <seealso marker="sasl:script">script(4)</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 [email protected] -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 63ec18d939..5513cb2d7e 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 a8fe865d9a..6fc05e153e 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 26100afc93..09317094c7 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,10 @@ # include <systemd/sd-daemon.h> #endif /* HAVE_SYSTEMD_DAEMON */ +#if defined(HAVE_IN6) && defined(AF_INET6) && defined(HAVE_INET_PTON) +# define EPMD6 +#endif + /* ************************************************************************ */ /* Replace some functions by others by making the function name a macro */ @@ -183,33 +188,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)) + +#if defined(EPMD6) -#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 +#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 +244,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 7100855407..55ec0f7b6c 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" @@ -1005,15 +1056,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]; @@ -1024,20 +1066,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"); @@ -1045,7 +1074,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"); @@ -1063,6 +1092,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 4de65500e9..d5837e5b8c 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, @@ -113,7 +114,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, @@ -172,6 +174,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) -> @@ -245,9 +265,14 @@ 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) -> Req = alive2_req(Port, NodeType, Prot, HVsn, LVsn, Name, 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]} -> @@ -1186,7 +1211,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 -> |